Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions core/src/main/java/fj/data/List.java
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,112 @@ public final A mode(final Ord<A> o) {
return sort(o).group(o.equal()).maximum(intOrd.comap(List.<A>length_())).head();
}

/**
* Groups the elements of this list by a given keyFunction into a {@link TreeMap}.
* The ordering of the keys is determined by {@link fj.Ord#hashOrd()}.
*
* @param keyFunction The function to select the keys for the map.
* @return A TreeMap containing the keys with the accumulated list of matched elements.
*/
public final <B> TreeMap<B, List<A>> groupBy(final F<A, B> keyFunction) {
return groupBy(keyFunction, Ord.hashOrd());
}

/**
* Groups the elements of this list by a given keyFunction into a {@link TreeMap}.
*
* @param keyFunction The function to select the keys for the map.
* @param keyOrd An order for the keys of the tree map.
* @return A TreeMap containing the keys with the accumulated list of matched elements.
*/
public final <B> TreeMap<B, List<A>> groupBy(final F<A, B> keyFunction, final Ord<B> keyOrd) {
return groupBy(keyFunction, Function.identity(), keyOrd);
}

/**
* Groups the elements of this list by a given keyFunction into a {@link TreeMap} and transforms
* the matching elements with the given valueFunction. The ordering of the keys is determined by
* {@link fj.Ord#hashOrd()}.
*
* @param keyFunction The function to select the keys for the map.
* @param valueFunction The function to apply on each matching value.
* @return A TreeMap containing the keys with the accumulated list of matched and mapped elements.
*/
public final <B, C> TreeMap<B, List<C>> groupBy(
final F<A, B> keyFunction,
final F<A, C> valueFunction) {
return this.groupBy(keyFunction, valueFunction, Ord.hashOrd());
}

/**
* Groups the elements of this list by a given keyFunction into a {@link TreeMap} and transforms
* the matching elements with the given valueFunction. The ordering of the keys is determined by
* the keyOrd parameter.
*
* @param keyFunction The function to select the keys for the map.
* @param valueFunction The function to apply on each matching value.
* @param keyOrd An order for the keys of the tree map.
* @return A TreeMap containing the keys with the accumulated list of matched and mapped elements.
*/
public final <B, C> TreeMap<B, List<C>> groupBy(
final F<A, B> keyFunction,
final F<A, C> valueFunction,
final Ord<B> keyOrd) {
return this.groupBy(keyFunction, valueFunction, List.<C>nil(), List::cons, keyOrd);
}

/**
* Groups the elements of this list by a given keyFunction into a {@link TreeMap} and transforms
* the matching elements with the given valueFunction. The ordering of the keys is determined by
* the keyOrd parameter.
*
* @param keyFunction The function to select the keys for the map.
* @param valueFunction The function to apply on each matching value.
* @param monoid A monoid, which defines the accumulator for the values and the zero value.
* @param keyOrd An order for the keys of the tree map.
* @return A TreeMap containing the keys with the accumulated list of matched and mapped elements.
*/
public final <B, C> TreeMap<B, C> groupBy(
final F<A, B> keyFunction,
final F<A, C> valueFunction,
final Monoid<C> monoid,
final Ord<B> keyOrd) {
return groupBy(keyFunction, valueFunction, monoid.zero(),
Function.uncurryF2(monoid.sum()), keyOrd);
}

/**
* Groups the elements of this list by a given keyFunction, applies the valueFunction and
* accumulates the mapped values with the given grouping accumulator function on the grouping
* identity.
*
* @param keyFunction The function to select the keys.
* @param valueFunction The function to apply on each element.
* @param groupingIdentity The identity, or start value, for the grouping.
* @param groupingAcc The accumulator to apply on each matching value.
* @param keyOrd An order for the keys of the tree map.
* @return A TreeMap containing the keys with the accumulated result of matched and mapped
* elements.
*/
public final <B, C, D> TreeMap<B, D> groupBy(
final F<A, B> keyFunction,
final F<A, C> valueFunction,
final D groupingIdentity,
final F2<C, D, D> groupingAcc,
final Ord<B> keyOrd) {
return this.foldLeft(map -> element -> {
final B key = keyFunction.f(element);
final C value = valueFunction.f(element);
map.set(key, map.get(key)
.map(existing -> groupingAcc.f(value, existing))
.orSome(groupingAcc.f(value, groupingIdentity)));
return map;
}, TreeMap.<B, D>empty(keyOrd)
);
}



/**
* Returns whether or not all elements in the list are equal according to the given equality test.
*
Expand Down
48 changes: 48 additions & 0 deletions demo/src/main/java/fj/demo/List_groupBy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package fj.demo;

import fj.Function;
import fj.Ord;
import fj.data.List;
import fj.data.TreeMap;

import static fj.data.List.list;

public final class List_groupBy {

public static void main(final String... args) {
keyDemo();
keyValueDemo();
keyValueAccDemo();
}

private static void keyDemo() {
System.out.println("KeyDemo");
final List<String> words = list("Hello", "World", "how", "are", "your", "doing");
final TreeMap<Integer, List<String>> lengthMap = words.groupBy(String::length);

lengthMap.forEach(entry -> {
System.out.println(String.format("Words with %d chars: %s", entry._1(), entry._2()));
});
}

private static void keyValueDemo() {
System.out.println("KeyValueDemo");
final List<Integer> xs = list(1, 2, 3, 4, 5, 6, 7, 8, 9);
final TreeMap<Integer, List<String>> result = xs.groupBy(x -> x % 3, Integer::toBinaryString);

result.forEach(entry -> {
System.out.println(String.format("Numbers with reminder %d are %s", entry._1(), entry._2()));
});
}

private static void keyValueAccDemo() {
System.out.println("KeyValueAccDemo");
final List<String> words = list("Hello", "World", "how", "are", "your", "doing");
final TreeMap<Integer, Integer> lengthCounts =
words.groupBy(String::length, Function.identity(), 0, (word, sum) -> sum + 1, Ord.hashOrd());

lengthCounts.forEach(entry -> {
System.out.println(String.format("Words with %d chars: %s", entry._1(), entry._2()));
});
}
}
22 changes: 22 additions & 0 deletions tests/src/test/scala/fj/data/CheckList.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package fj
package data

import java.lang

import fj.Monoid
import org.scalacheck.Prop._
import ArbitraryList.arbitraryList
import ArbitraryP.arbitraryP1
Expand Down Expand Up @@ -174,6 +177,25 @@ object CheckList extends Properties("List") {
a.foldRight((a: List[String], b: List[String]) => a.append(b), nil[String]),
join(a)))

property("groupBy") = forAll((a: List[Int]) => {
val result = a.groupBy((x: Int) => (x % 2 == 0): lang.Boolean)
result.get(true).forall((xs: List[Int]) => xs.forall((x: Int) => (x % 2 == 0): lang.Boolean): lang.Boolean) &&
result.get(false).forall((xs: List[Int]) => xs.forall((x: Int) => (x % 2 != 0): lang.Boolean): lang.Boolean)
})

property("groupByMonoid") = forAll((a: List[Int]) => {
val result = a.groupBy((x: Int) => (x % 2 == 0): lang.Boolean, (x: Int) => x: lang.Integer, Monoid.intAdditionMonoid, Ord.booleanOrd)
result.get(true).forall((x: lang.Integer) =>
x == a.filter((x: Int) => (x % 2 == 0): lang.Boolean).
map((x: Int) => x:lang.Integer).
foldLeft(Function.uncurryF2[lang.Integer, lang.Integer, lang.Integer](Monoid.intAdditionMonoid.sum), Monoid.intAdditionMonoid.zero()): lang.Boolean) &&
result.get(false).forall((x: lang.Integer) =>
x == a.filter((x: Int) => (x % 2 != 0): lang.Boolean).
map((x: Int) => x:lang.Integer).
foldLeft(Function.uncurryF2[lang.Integer, lang.Integer, lang.Integer](Monoid.intAdditionMonoid.sum), Monoid.intAdditionMonoid.zero()): lang.Boolean)
})


/*property("iterateWhile") = forAll((n: Int) => n > 0 ==>
(iterateWhile(((x:Int) => x - 1), ((x:Int) => ((x > 0): java.lang.Boolean)), n).length == n))*/
}