package fj.data.properties;
import fj.*;
import fj.data.List;
import fj.data.Stream;
import fj.data.TreeMap;
import fj.test.reflect.CheckParams;
import fj.test.runner.PropertyTestRunner;
import fj.test.Gen;
import fj.test.Property;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Collections;
import static fj.Equal.listEqual;
import static fj.Equal.p2Equal;
import static fj.Function.compose;
import static fj.Function.identity;
import static fj.P.p;
import static fj.data.List.nil;
import static fj.data.List.single;
import static fj.test.Arbitrary.*;
import static fj.test.Property.implies;
import static fj.test.Property.prop;
import static fj.test.Property.property;
import static fj.Equal.intEqual;
import static fj.Monoid.intAdditionMonoid;
import static fj.Ord.booleanOrd;
import static fj.Ord.intOrd;
@RunWith(PropertyTestRunner.class)
@CheckParams(maxSize = 10000)
public class ListProperties {
private static final Equal<List<Integer>> eq = listEqual(intEqual);
private static final Gen<P2<List<Integer>, Integer>> arbListWithIndex = arbList(arbInteger)
.filter(List::isNotEmpty)
.bind(list -> Gen.choose(0, list.length() - 1).map(i -> p(list, i)));
public Property isEmpty() {
return property(arbList(arbInteger), list -> prop(list.isEmpty() != list.isNotEmpty()));
}
public Property isNotEmpty() {
return property(arbList(arbInteger), list -> prop(list.length() > 0 == list.isNotEmpty()));
}
public Property orHead() {
return property(arbList(arbInteger), arbInteger, (list, n) ->
implies(list.isNotEmpty(), () -> prop(intEqual.eq(list.orHead(() -> n), list.head()))));
}
public Property orTail() {
return property(arbList(arbInteger), arbP1(arbList(arbInteger)), (list, list2) ->
implies(list.isNotEmpty(), () -> prop(eq.eq(list.orTail(list2), list.tail()))));
}
public Property toOption() {
return property(arbList(arbInteger), list ->
prop(list.headOption().isNone() || intEqual.eq(list.headOption().some(), list.head())));
}
public Property consHead() {
return property(arbList(arbInteger), arbInteger, (list, n) -> prop(intEqual.eq(list.cons(n).head(), n)));
}
public Property consLength() {
return property(arbList(arbInteger), arbInteger, (list, n) -> prop(list.cons(n).length() == list.length() + 1));
}
public Property mapId() {
return property(arbList(arbInteger), list -> prop(eq.eq(list.map(identity()), list)));
}
public Property mapCompose() {
final F<Integer, Integer> f = x -> x + 3;
final F<Integer, Integer> g = x -> x * 4;
return property(arbList(arbInteger), list ->
prop(eq.eq(list.map(compose(f, g)), list.map(g).map(f))));
}
public Property foreachDoEffect() {
return property(arbList(arbInteger), list -> {
int[] acc = {0};
list.foreachDoEffect(x -> acc[0] += x);
int acc2 = 0;
for (int x : list) { acc2 += x; }
return prop(intEqual.eq(acc[0], acc2));
});
}
public Property filter() {
final F<Integer, Boolean> predicate = (x -> x % 2 == 0);
return property(arbList(arbInteger), list -> prop(list.filter(predicate).forall(predicate)));
}
public Property filterLength() {
final F<Integer, Boolean> predicate = (x -> x % 2 == 0);
return property(arbList(arbInteger), list -> prop(list.filter(predicate).length() <= list.length()));
}
public Property bindLeftIdentity() {
final F<Integer, List<Integer>> f = (i -> single(-i));
return property(arbList(arbInteger), arbInteger, (list, i) ->
prop(eq.eq(single(i).bind(f), f.f(i))));
}
public Property bindRightIdentity() {
return property(arbList(arbInteger), list -> prop(eq.eq(list.bind(List::list), list)));
}
public Property bindAssociativity() {
final F<Integer, List<Integer>> f = x -> single(x + 3);
final F<Integer, List<Integer>> g = x -> single(x * 4);
return property(arbList(arbInteger), list ->
prop(eq.eq(list.bind(f).bind(g), list.bind(i -> f.f(i).bind(g)))));
}
public Property foldRight() {
return property(arbList(arbInteger), list ->
prop(eq.eq(list.foldRight((i, s) -> single(i).append(s), nil()), list)));
}
public Property foldLeft() {
return property(arbList(arbInteger), list ->
prop(eq.eq(list.foldLeft((s, i) -> single(i).append(s), nil()),
list.reverse().foldRight((i, s) -> single(i).append(s), nil()))));
}
public Property tailLength() {
return property(arbList(arbInteger), list ->
implies(list.isNotEmpty(), () -> prop(list.tail().length() == list.length() - 1)));
}
public Property reverseIdentity() {
return property(arbList(arbInteger), list -> prop(eq.eq(list.reverse().reverse(), list)));
}
public Property reverse() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
prop(eq.eq(list1.append(list2).reverse(), list2.reverse().append(list1.reverse()))));
}
@CheckParams(maxSize = 100)
public Property sequence() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
prop(eq.eq(list1.sequence(list2), list1.bind(__ -> list2))));
}
public Property appendLeftIdentity() {
return property(arbList(arbInteger), list -> prop(eq.eq(List.<Integer> nil().append(list), list)));
}
public Property appendRightIdentity() {
return property(arbList(arbInteger), list -> prop(eq.eq(list.append(nil()), list)));
}
public Property appendAssociativity() {
return property(arbList(arbInteger), arbList(arbInteger), arbList(arbInteger), (list1, list2, list3) ->
prop(eq.eq(list1.append(list2).append(list3), list1.append(list2.append(list3)))));
}
public Property appendLength() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
prop(list1.append(list2).length() == list1.length() + list2.length()));
}
@CheckParams(minSize = 2, maxSize = 10000)
public Property indexTail() {
final Gen<P2<List<Integer>, Integer>> gen = arbList(arbInteger)
.filter(list -> list.length() > 1)
.bind(list -> Gen.choose(1, list.length() - 1).map(i -> p(list, i)));
return property(gen, pair -> {
final List<Integer> list = pair._1();
final int i = pair._2();
return prop(intEqual.eq(list.index(i), list.tail().index(i - 1)));
});
}
public Property snoc() {
return property(arbList(arbInteger), arbInteger, (list, n) -> prop(eq.eq(list.snoc(n), list.append(single(n)))));
}
public Property take() {
return property(arbList(arbInteger), arbInteger, (list, n) -> prop(list.take(n).length() <= list.length()));
}
public Property drop() {
return property(arbList(arbInteger), arbInteger, (list, n) -> prop(list.drop(n).length() <= list.length()));
}
public Property splitAt() {
return property(arbList(arbInteger), arbInteger, (list, n) ->
prop(p2Equal(eq, eq).eq(list.splitAt(n), p(list.take(n), list.drop(n)))));
}
@CheckParams(minSize = 1, maxSize = 2000)
public Property partition() {
return property(arbListWithIndex, p -> implies(p._2() > 0, () -> {
final List<Integer> list = p._1();
final Integer i = p._2();
final List<List<Integer>> partition = list.partition(i);
return prop(eq.eq(list, List.join(partition))).and(prop(partition.forall(part -> part.length() <= i)));
}));
}
@CheckParams(minSize = 1, maxSize = 2000)
public Property tails() {
return property(arbList(arbInteger), list -> implies(list.isNotEmpty(), () ->
prop(list.tails().length() == list.length() + 1 &&
List.join(list.inits()).length() == Stream.range(1, list.length() + 1).foldLeft((acc, i) -> acc + i, 0))));
}
@CheckParams(minSize = 1, maxSize = 2000)
public Property inits() {
return property(arbList(arbInteger), list -> implies(list.isNotEmpty(), () ->
prop(list.inits().length() == list.length() + 1 &&
List.join(list.tails()).length() == Stream.range(1, list.length() + 1).foldLeft((acc, i) -> acc + i, 0))));
}
public Property sort() {
return property(arbList(arbInteger), list -> {
java.util.List<Integer> javaList = list.sort(intOrd).toJavaList();
java.util.List<Integer> copy = new ArrayList<>(javaList);
Collections.sort(copy);
return prop(javaList.equals(copy));
});
}
public Property forallExists() {
return property(arbList(arbInteger), list ->
prop(list.forall(x -> x % 2 == 0) == !list.exists(x -> x % 2 != 0)));
}
public Property find() {
return property(arbList(arbInteger), list -> prop(list.find(x -> x % 2 == 0).forall(x -> x % 2 == 0)));
}
@CheckParams(maxSize = 500)
public Property join() {
return property(arbList(arbList(arbInteger)), (List<List<Integer>> lists) ->
prop(eq.eq(lists.foldLeft(List::append, nil()), List.join(lists))));
}
@CheckParams(maxSize = 2000)
public Property nub() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
prop(eq.eq(list1.append(list2).nub(), list1.nub().append(list2.nub()).nub())));
}
public Property groupBy() {
return property(arbList(arbInteger), list -> {
final TreeMap<Boolean, List<Integer>> map = list.groupBy(i -> i % 2 == 0, Ord.booleanOrd);
final List<Integer> list1 = map.get(true).orSome(nil());
final List<Integer> list2 = map.get(false).orSome(nil());
return prop(list.length() == list1.length() + list2.length())
.and(prop(list1.forall(i -> i % 2 == 0)))
.and(prop(list2.forall(i -> i % 2 != 0)))
.and(prop(list.map(i -> i % 2 == 0).nub().length() == map.size()));
});
}
public Property groupByMonoid() {
return property(arbList(arbInteger), list -> {
final TreeMap<Boolean, Integer> map = list.groupBy(i -> i % 2 == 0, identity(), intAdditionMonoid, booleanOrd);
final int sum1 = map.get(true).orSome(0);
final int sum2 = map.get(false).orSome(0);
return prop(list.filter(i -> i % 2 == 0).foldLeft((acc, i) -> acc + i, 0) == sum1)
.and(prop(list.filter(i -> i % 2 != 0).foldLeft((acc, i) -> acc + i, 0) == sum2));
});
}
public Property isPrefixOf() {
final Gen<P2<List<Integer>, Integer>> gen = arbList(arbInteger).bind(list ->
Gen.choose(0, list.length()).map(i -> p(list, i)));
return property(gen, pair -> prop(pair._1().take(pair._2()).isPrefixOf(intEqual, pair._1())));
}
public Property isSuffixOf() {
final Gen<P2<List<Integer>, Integer>> gen = arbList(arbInteger).bind(list ->
Gen.choose(0, list.length()).map(i -> p(list, i)));
return property(gen, pair -> prop(pair._1().drop(pair._2()).isSuffixOf(intEqual, pair._1())));
}
public Property isPrefixOfShorter() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
implies(list1.length() > list2.length(), () -> prop(!list1.isPrefixOf(intEqual, list2))));
}
public Property isSuffixOfShorter() {
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
implies(list1.length() > list2.length(), () -> prop(!list1.isSuffixOf(intEqual, list2))));
}
public Property isPrefixOfDifferentHeads() {
return property(arbList(arbInteger), arbList(arbInteger), arbInteger, arbInteger, (list1, list2, h1, h2) ->
implies(intEqual.notEq(h1, h2), () -> prop(!list1.cons(h1).isPrefixOf(intEqual, list2.cons(h2)))));
}
public Property isSuffixOfDifferentHeads() {
return property(arbList(arbInteger), arbList(arbInteger), arbInteger, arbInteger, (list1, list2, h1, h2) ->
implies(intEqual.notEq(h1, h2), () -> prop(!list1.snoc(h1).isSuffixOf(intEqual, list2.snoc(h2)))));
}
public Property listOrdEqual() {
return property(arbList(arbInteger), list -> prop(Ord.listOrd(Ord.intOrd).equal().eq(list, list)));
}
public Property listOrdReverse() {
final Ord<List<Integer>> ord = Ord.listOrd(Ord.intOrd);
return property(arbList(arbInteger), arbList(arbInteger), (list1, list2) ->
prop(ord.compare(list1, list2) == ord.reverse().compare(list1, list2).reverse()));
}
}