[functional] Add Stream.filterNot

This commit is contained in:
aNNiMON 2023-10-05 16:19:26 +03:00 committed by Victor Melnik
parent fb6a8b8ea1
commit 110aa6264a
7 changed files with 212 additions and 141 deletions

View File

@ -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;
});
}
}

View File

@ -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());

View File

@ -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);
return filter(container, predicate);
}
static Value filter(Value container, Function predicate) {
if (container.type() == Types.ARRAY) {
return filterArray((ArrayValue) container, predicate, takeWhile);
return filterArray((ArrayValue) container, predicate);
}
if (container.type() == Types.MAP) {
return filterMap((MapValue) container, predicate, takeWhile);
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<Value> 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<Value, Value> element : map) {
if (predicate.execute(element.getKey(), element.getValue()) != NumberValue.ZERO) {
result.set(element.getKey(), element.getValue());
} else if (takeWhile) break;
}
}
return result;
}

View File

@ -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);
}
}

View File

@ -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;
});
}
}
}

View File

@ -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<Value> 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<Value, Value> element : map) {
if (predicate.execute(element.getKey(), element.getValue()) != NumberValue.ZERO) {
result.set(element.getKey(), element.getValue());
} else break;
}
return result;
}
}

View File

@ -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())