diff --git a/core/src/main/java/fj/data/List.java b/core/src/main/java/fj/data/List.java
index a957bc5d..c60363c3 100644
--- a/core/src/main/java/fj/data/List.java
+++ b/core/src/main/java/fj/data/List.java
@@ -1237,6 +1237,112 @@ public final A mode(final Ord o) {
return sort(o).group(o.equal()).maximum(intOrd.comap(List.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 TreeMap> groupBy(final F 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 TreeMap> groupBy(final F keyFunction, final Ord 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 TreeMap> groupBy(
+ final F keyFunction,
+ final F 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 TreeMap> groupBy(
+ final F keyFunction,
+ final F valueFunction,
+ final Ord keyOrd) {
+ return this.groupBy(keyFunction, valueFunction, List.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 TreeMap groupBy(
+ final F keyFunction,
+ final F valueFunction,
+ final Monoid monoid,
+ final Ord 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 TreeMap groupBy(
+ final F keyFunction,
+ final F valueFunction,
+ final D groupingIdentity,
+ final F2 groupingAcc,
+ final Ord 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.empty(keyOrd)
+ );
+ }
+
+
+
/**
* Returns whether or not all elements in the list are equal according to the given equality test.
*
diff --git a/demo/src/main/java/fj/demo/List_groupBy.java b/demo/src/main/java/fj/demo/List_groupBy.java
new file mode 100644
index 00000000..1dc8866d
--- /dev/null
+++ b/demo/src/main/java/fj/demo/List_groupBy.java
@@ -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 words = list("Hello", "World", "how", "are", "your", "doing");
+ final TreeMap> 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 xs = list(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ final TreeMap> 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 words = list("Hello", "World", "how", "are", "your", "doing");
+ final TreeMap 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()));
+ });
+ }
+}
diff --git a/tests/src/test/scala/fj/data/CheckList.scala b/tests/src/test/scala/fj/data/CheckList.scala
index 807e1293..90e7b1d7 100644
--- a/tests/src/test/scala/fj/data/CheckList.scala
+++ b/tests/src/test/scala/fj/data/CheckList.scala
@@ -1,6 +1,9 @@
package fj
package data
+import java.lang
+
+import fj.Monoid
import org.scalacheck.Prop._
import ArbitraryList.arbitraryList
import ArbitraryP.arbitraryP1
@@ -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))*/
}