From 110aa6264a9d1cc03458506b64542b8ff039d207 Mon Sep 17 00:00:00 2001 From: aNNiMON Date: Thu, 5 Oct 2023 16:19:26 +0300 Subject: [PATCH] [functional] Add Stream.filterNot --- .../modules/functional/StreamValue.java | 116 ++++++++++++++++++ .../modules/functional/functional.java | 8 +- .../modules/functional/functional_filter.java | 37 +++--- .../functional/functional_filterNot.java | 18 +++ .../modules/functional/functional_stream.java | 115 ----------------- .../functional/functional_takeWhile.java | 49 ++++++++ .../resources/modules/functional/stream.own | 10 ++ 7 files changed, 212 insertions(+), 141 deletions(-) create mode 100644 modules/main/src/main/java/com/annimon/ownlang/modules/functional/StreamValue.java create mode 100644 modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filterNot.java create mode 100644 modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_takeWhile.java diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/StreamValue.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/StreamValue.java new file mode 100644 index 0000000..90d0abc --- /dev/null +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/StreamValue.java @@ -0,0 +1,116 @@ +package com.annimon.ownlang.modules.functional; + +import com.annimon.ownlang.exceptions.ArgumentsMismatchException; +import com.annimon.ownlang.lib.*; +import java.util.Arrays; + +class StreamValue extends MapValue { + + private final ArrayValue container; + + public StreamValue(ArrayValue container) { + super(16); + this.container = container; + init(); + } + + private void init() { + set("filter", wrapIntermediate(new functional_filter())); + set("filterNot", wrapIntermediate(new functional_filterNot())); + set("map", wrapIntermediate(new functional_map())); + set("flatMap", wrapIntermediate(new functional_flatmap())); + set("sorted", this::sorted); + set("sortBy", wrapIntermediate(new functional_sortby())); + set("takeWhile", wrapIntermediate(new functional_takeWhile())); + set("dropWhile", wrapIntermediate(new functional_dropwhile())); + set("peek", wrapIntermediate(new functional_foreach())); + set("skip", this::skip); + set("limit", this::limit); + set("custom", this::custom); + + set("reduce", wrapTerminal(new functional_reduce())); + set("forEach", wrapTerminal(new functional_foreach())); + set("toArray", args -> container); + set("joining", container::joinToString); + set("count", args -> NumberValue.of(container.size())); + } + + private Value skip(Value[] args) { + Arguments.check(1, args.length); + + final int skipCount = args[0].asInt(); + final int size = container.size(); + + if (skipCount <= 0) return this; + if (skipCount >= size) { + return new StreamValue(new ArrayValue(0)); + } + + final Value[] result = new Value[size - skipCount]; + System.arraycopy(container.getCopyElements(), skipCount, result, 0, result.length); + return new StreamValue(new ArrayValue(result)); + } + + private Value limit(Value[] args) { + Arguments.check(1, args.length); + + final int limitCount = args[0].asInt(); + final int size = container.size(); + + if (limitCount >= size) return this; + if (limitCount <= 0) { + return new StreamValue(new ArrayValue(0)); + } + + final Value[] result = new Value[limitCount]; + System.arraycopy(container.getCopyElements(), 0, result, 0, limitCount); + return new StreamValue(new ArrayValue(result)); + } + + private Value sorted(Value[] args) { + Arguments.checkOrOr(0, 1, args.length); + final Value[] elements = container.getCopyElements(); + + switch (args.length) { + case 0 -> Arrays.sort(elements); + case 1 -> { + final Function comparator = ValueUtils.consumeFunction(args[0], 0); + Arrays.sort(elements, (o1, o2) -> comparator.execute(o1, o2).asInt()); + } + default -> throw new ArgumentsMismatchException("Wrong number of arguments"); + } + + return new StreamValue(new ArrayValue(elements)); + } + + private Value custom(Value[] args) { + Arguments.check(1, args.length); + final Function f = ValueUtils.consumeFunction(args[0], 0); + final Value result = f.execute(container); + if (result.type() == Types.ARRAY) { + return new StreamValue((ArrayValue) result); + } + return result; + } + + private FunctionValue wrapIntermediate(Function f) { + return wrap(f, true); + } + + private FunctionValue wrapTerminal(Function f) { + return wrap(f, false); + } + + private FunctionValue wrap(Function f, boolean intermediate) { + return new FunctionValue(args -> { + final Value[] newArgs = new Value[args.length + 1]; + System.arraycopy(args, 0, newArgs, 1, args.length); + newArgs[0] = container; + final Value result = f.execute(newArgs); + if (intermediate && result.type() == Types.ARRAY) { + return new StreamValue((ArrayValue) result); + } + return result; + }); + } +} \ No newline at end of file diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional.java index 0ad6188..66c0dcc 100644 --- a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional.java +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional.java @@ -1,8 +1,6 @@ package com.annimon.ownlang.modules.functional; -import com.annimon.ownlang.lib.Function; -import com.annimon.ownlang.lib.FunctionValue; -import com.annimon.ownlang.lib.Value; +import com.annimon.ownlang.lib.*; import com.annimon.ownlang.modules.Module; import java.util.HashMap; import java.util.Map; @@ -25,9 +23,9 @@ public final class functional implements Module { result.put("map", new functional_map()); result.put("flatmap", new functional_flatmap()); result.put("reduce", new functional_reduce()); - result.put("filter", new functional_filter(false)); + result.put("filter", new functional_filter()); result.put("sortby", new functional_sortby()); - result.put("takewhile", new functional_filter(true)); + result.put("takewhile", new functional_takeWhile()); result.put("dropwhile", new functional_dropwhile()); result.put("chain", new functional_chain()); diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filter.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filter.java index 25c73d6..6f1c1ca 100644 --- a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filter.java +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filter.java @@ -8,46 +8,41 @@ import java.util.Map; public final class functional_filter implements Function { - private final boolean takeWhile; - - public functional_filter(boolean takeWhile) { - this.takeWhile = takeWhile; - } - @Override public Value execute(Value[] args) { Arguments.check(2, args.length); final Value container = args[0]; final Function predicate = ValueUtils.consumeFunction(args[1], 1); - if (container.type() == Types.ARRAY) { - return filterArray((ArrayValue) container, predicate, takeWhile); - } - - if (container.type() == Types.MAP) { - return filterMap((MapValue) container, predicate, takeWhile); - } + return filter(container, predicate); + } + static Value filter(Value container, Function predicate) { + if (container.type() == Types.ARRAY) { + return filterArray((ArrayValue) container, predicate); + } + if (container.type() == Types.MAP) { + return filterMap((MapValue) container, predicate); + } throw new TypeException("Invalid first argument. Array or map expected"); } - - private Value filterArray(ArrayValue array, Function predicate, boolean takeWhile) { + + static ArrayValue filterArray(ArrayValue array, Function predicate) { final int size = array.size(); final List values = new ArrayList<>(size); for (Value value : array) { if (predicate.execute(value) != NumberValue.ZERO) { values.add(value); - } else if (takeWhile) break; + } } - final int newSize = values.size(); - return new ArrayValue(values.toArray(new Value[newSize])); + return new ArrayValue(values); } - - private Value filterMap(MapValue map, Function predicate, boolean takeWhile) { + + static MapValue filterMap(MapValue map, Function predicate) { final MapValue result = new MapValue(map.size()); for (Map.Entry element : map) { if (predicate.execute(element.getKey(), element.getValue()) != NumberValue.ZERO) { result.set(element.getKey(), element.getValue()); - } else if (takeWhile) break; + } } return result; } diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filterNot.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filterNot.java new file mode 100644 index 0000000..7ab6e20 --- /dev/null +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_filterNot.java @@ -0,0 +1,18 @@ +package com.annimon.ownlang.modules.functional; + +import com.annimon.ownlang.lib.*; + +public final class functional_filterNot implements Function { + + @Override + public Value execute(Value[] args) { + Arguments.check(2, args.length); + final Value container = args[0]; + final Function predicate = ValueUtils.consumeFunction(args[1], 1); + return functional_filter.filter(container, negate(predicate)); + } + + static Function negate(Function f) { + return args -> NumberValue.fromBoolean(f.execute(args) == NumberValue.ZERO); + } +} diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_stream.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_stream.java index f0888aa..5ba6d70 100644 --- a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_stream.java +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_stream.java @@ -1,9 +1,7 @@ package com.annimon.ownlang.modules.functional; -import com.annimon.ownlang.exceptions.ArgumentsMismatchException; import com.annimon.ownlang.exceptions.TypeException; import com.annimon.ownlang.lib.*; -import java.util.Arrays; public final class functional_stream implements Function { @@ -25,117 +23,4 @@ public final class functional_stream implements Function { throw new TypeException("Invalid argument. Array or map expected"); } } - - private static class StreamValue extends MapValue { - - private final ArrayValue container; - - public StreamValue(ArrayValue container) { - super(16); - this.container = container; - init(); - } - - private void init() { - set("filter", wrapIntermediate(new functional_filter(false))); - set("map", wrapIntermediate(new functional_map())); - set("flatMap", wrapIntermediate(new functional_flatmap())); - set("sorted", this::sorted); - set("sortBy", wrapIntermediate(new functional_sortby())); - set("takeWhile", wrapIntermediate(new functional_filter(true))); - set("dropWhile", wrapIntermediate(new functional_dropwhile())); - set("peek", wrapIntermediate(new functional_foreach())); - set("skip", this::skip); - set("limit", this::limit); - set("custom", this::custom); - - set("reduce", wrapTerminal(new functional_reduce())); - set("forEach", wrapTerminal(new functional_foreach())); - set("toArray", args -> container); - set("joining", container::joinToString); - set("count", args -> NumberValue.of(container.size())); - } - - private Value skip(Value[] args) { - Arguments.check(1, args.length); - - final int skipCount = args[0].asInt(); - final int size = container.size(); - - if (skipCount <= 0) return this; - if (skipCount >= size) { - return new StreamValue(new ArrayValue(0)); - } - - final Value[] result = new Value[size - skipCount]; - System.arraycopy(container.getCopyElements(), skipCount, result, 0, result.length); - return new StreamValue(new ArrayValue(result)); - } - - private Value limit(Value[] args) { - Arguments.check(1, args.length); - - final int limitCount = args[0].asInt(); - final int size = container.size(); - - if (limitCount >= size) return this; - if (limitCount <= 0) { - return new StreamValue(new ArrayValue(0)); - } - - final Value[] result = new Value[limitCount]; - System.arraycopy(container.getCopyElements(), 0, result, 0, limitCount); - return new StreamValue(new ArrayValue(result)); - } - - private Value sorted(Value[] args) { - Arguments.checkOrOr(0, 1, args.length); - final Value[] elements = container.getCopyElements(); - - switch (args.length) { - case 0: - Arrays.sort(elements); - break; - case 1: - final Function comparator = ValueUtils.consumeFunction(args[0], 0); - Arrays.sort(elements, (o1, o2) -> comparator.execute(o1, o2).asInt()); - break; - default: - throw new ArgumentsMismatchException("Wrong number of arguments"); - } - - return new StreamValue(new ArrayValue(elements)); - } - - private Value custom(Value[] args) { - Arguments.check(1, args.length); - final Function f = ValueUtils.consumeFunction(args[0], 0); - final Value result = f.execute(container); - if (result.type() == Types.ARRAY) { - return new StreamValue((ArrayValue) result); - } - return result; - } - - private FunctionValue wrapIntermediate(Function f) { - return wrap(f, true); - } - - private FunctionValue wrapTerminal(Function f) { - return wrap(f, false); - } - - private FunctionValue wrap(Function f, boolean intermediate) { - return new FunctionValue(args -> { - final Value[] newArgs = new Value[args.length + 1]; - System.arraycopy(args, 0, newArgs, 1, args.length); - newArgs[0] = container; - final Value result = f.execute(newArgs); - if (intermediate && result.type() == Types.ARRAY) { - return new StreamValue((ArrayValue) result); - } - return result; - }); - } - } } diff --git a/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_takeWhile.java b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_takeWhile.java new file mode 100644 index 0000000..3a0569d --- /dev/null +++ b/modules/main/src/main/java/com/annimon/ownlang/modules/functional/functional_takeWhile.java @@ -0,0 +1,49 @@ +package com.annimon.ownlang.modules.functional; + +import com.annimon.ownlang.exceptions.TypeException; +import com.annimon.ownlang.lib.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public final class functional_takeWhile implements Function { + + @Override + public Value execute(Value[] args) { + Arguments.check(2, args.length); + final Value container = args[0]; + final Function predicate = ValueUtils.consumeFunction(args[1], 1); + return takeWhile(container, predicate); + } + + static Value takeWhile(Value container, Function predicate) { + if (container.type() == Types.ARRAY) { + return takeWhileArray((ArrayValue) container, predicate); + } + if (container.type() == Types.MAP) { + return takeWhileMap((MapValue) container, predicate); + } + throw new TypeException("Invalid first argument. Array or map expected"); + } + + static ArrayValue takeWhileArray(ArrayValue array, Function predicate) { + final int size = array.size(); + final List values = new ArrayList<>(size); + for (Value value : array) { + if (predicate.execute(value) != NumberValue.ZERO) { + values.add(value); + } else break; + } + return new ArrayValue(values); + } + + static MapValue takeWhileMap(MapValue map, Function predicate) { + final MapValue result = new MapValue(map.size()); + for (Map.Entry element : map) { + if (predicate.execute(element.getKey(), element.getValue()) != NumberValue.ZERO) { + result.set(element.getKey(), element.getValue()); + } else break; + } + return result; + } +} diff --git a/ownlang-parser/src/test/resources/modules/functional/stream.own b/ownlang-parser/src/test/resources/modules/functional/stream.own index 9a4b405..aba004c 100644 --- a/ownlang-parser/src/test/resources/modules/functional/stream.own +++ b/ownlang-parser/src/test/resources/modules/functional/stream.own @@ -10,6 +10,16 @@ def testStream() { assertEquals([8,6,4,2], result) } +def testFilter() { + data = [1,2,3,4,5,6,7] + assertEquals([2, 4, 6], stream(data).filter(def(x) = x % 2 == 0).toArray()) +} + +def testFilterNot() { + data = [1,2,3,4,5,6,7] + assertEquals([1, 3, 5, 7], stream(data).filterNot(def(x) = x % 2 == 0).toArray()) +} + def testSkip() { data = [1,2,3,4,5,6,7] assertEquals(7, stream(data).skip(0).count())