Skip to content

Commit c3a893e

Browse files
committed
[functional] Add groupby and Stream.groupBy
1 parent 7a64d7f commit c3a893e

File tree

5 files changed

+108
-8
lines changed

5 files changed

+108
-8
lines changed

modules/main/src/main/java/com/annimon/ownlang/modules/functional/StreamValue.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ private void init() {
3131
set("reduce", wrapTerminal(new functional_reduce()));
3232
set("forEach", wrapTerminal(new functional_forEach()));
3333
set("forEachIndexed", wrapTerminal(new functional_forEachIndexed()));
34+
set("groupBy", wrapTerminal(new functional_groupBy()));
3435
set("toArray", args -> container);
3536
set("joining", container::joinToString);
3637
set("count", args -> NumberValue.of(container.size()));

modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public Map<String, Function> functions() {
2727
result.put("sortby", new functional_sortBy());
2828
result.put("takewhile", new functional_takeWhile());
2929
result.put("dropwhile", new functional_dropWhile());
30+
result.put("groupby", new functional_groupBy());
3031

3132
result.put("chain", new functional_chain());
3233
result.put("stream", new functional_stream());
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.annimon.ownlang.modules.functional;
2+
3+
import com.annimon.ownlang.exceptions.TypeException;
4+
import com.annimon.ownlang.lib.*;
5+
import java.util.ArrayList;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public final class functional_groupBy implements Function {
11+
12+
@Override
13+
public Value execute(Value[] args) {
14+
Arguments.check(2, args.length);
15+
16+
final Value container = args[0];
17+
final Function classifier = ValueUtils.consumeFunction(args[1], 1);
18+
return groupBy(container, classifier);
19+
}
20+
21+
static Value groupBy(Value container, Function classifier) {
22+
if (container.type() == Types.ARRAY) {
23+
return groupByArray((ArrayValue) container, classifier);
24+
}
25+
if (container.type() == Types.MAP) {
26+
return groupByMap((MapValue) container, classifier);
27+
}
28+
throw new TypeException("Invalid first argument. Array or map expected");
29+
}
30+
31+
@SuppressWarnings("Java8MapApi")
32+
static Value groupByArray(ArrayValue array, Function classifier) {
33+
final var result = new LinkedHashMap<Value, List<Value>>();
34+
for (Value element : array) {
35+
final var key = classifier.execute(element);
36+
var container = result.get(key);
37+
if (container == null) {
38+
container = new ArrayList<>();
39+
result.put(key, container);
40+
}
41+
container.add(element);
42+
}
43+
return fromMapOfArrays(result);
44+
}
45+
46+
static Value groupByMap(MapValue map, Function classifier) {
47+
final var result = new LinkedHashMap<Value, Value>();
48+
for (Map.Entry<Value, Value> element : map) {
49+
final var k = element.getKey();
50+
final var v = element.getValue();
51+
final var key = classifier.execute(k, v);
52+
var container = (MapValue) result.get(key);
53+
if (container == null) {
54+
container = new MapValue(10);
55+
result.put(key, container);
56+
}
57+
container.set(k, v);
58+
}
59+
return new MapValue(result);
60+
}
61+
62+
private static MapValue fromMapOfArrays(Map<Value, List<Value>> map) {
63+
final var result = new LinkedHashMap<Value, Value>();
64+
map.forEach((key, value) -> result.put(key, new ArrayValue(value)));
65+
return new MapValue(result);
66+
}
67+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use std, functional
2+
3+
def testArraysGroupBy() {
4+
arr = [1, 2, 3, 4, 1, 2, 3, 1, 2, 3]
5+
result = groupby(arr, def(v) = v % 2 == 0)
6+
assertEquals([2, 4, 2, 2], result[true])
7+
assertEquals([1, 3, 1, 3, 1, 3], result[false])
8+
}
9+
10+
def testMapsGroupBy() {
11+
map = {"abc": 123, "test1": 234, "test2": 345, "test3": 456, "def": 567}
12+
result = groupby(map, def(k, v) = k.startsWith("test"))
13+
assertEquals({"test1": 234, "test2": 345, "test3": 456}, result[true])
14+
assertEquals({"abc": 123, "def": 567}, result[false])
15+
}

ownlang-parser/src/test/resources/modules/functional/stream.own

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ def testCustom() {
5353
assertEquals([5,6,4,2], stream(data).custom(::reverse).toArray())
5454
}
5555

56+
def reverse(container) {
57+
size = length(container)
58+
result = newarray(size)
59+
for i : range(size) {
60+
result[size - i - 1] = container[i]
61+
}
62+
return result
63+
}
64+
5665
def testJoining() {
5766
data = [1,2,3,4]
5867
assertEquals("1234", stream(data).joining())
@@ -101,11 +110,18 @@ def testForEachMapIndexed() {
101110
assertEquals("a10b21", result)
102111
}
103112

104-
def reverse(container) {
105-
size = length(container)
106-
result = newarray(size)
107-
for i : range(size) {
108-
result[size - i - 1] = container[i]
109-
}
110-
return result
111-
}
113+
def testArraysGroupBy() {
114+
data = [1, 2, 3, 4, 1, 2, 3, 1, 2, 3]
115+
result = stream(data)
116+
.groupBy(def(v) = v % 2 == 0)
117+
assertEquals([2, 4, 2, 2], result[true])
118+
assertEquals([1, 3, 1, 3, 1, 3], result[false])
119+
}
120+
121+
def testMapsGroupBy() {
122+
data = {"abc": 123, "test1": 234, "test2": 345, "test3": 456, "def": 567}
123+
result = stream(data)
124+
.groupBy(def(entry) = entry[0].startsWith("test"))
125+
assertEquals([["test1", 234], ["test2", 345], ["test3", 456]], sort(result[true]))
126+
assertEquals([["abc", 123], ["def", 567]], sort(result[false]))
127+
}

0 commit comments

Comments
 (0)