Using Java, I am trying to find a clean way to accumulate multiple different value in a series of lambda. For a concrete example see this following piece of JS (typescript) code:
// some filtering helpers. not really interested in that because there are similar things in Java
const mapFilter = <T,U>(arr: T[], transform: (item: T, idx: number, arr: T[]) => U) => arr.map(transform).filter(Boolean)
const mapFilterFlat = <T,U>(arr: T[], transform: (item: T, idx: number, arr: T[]) => U[]) => mapFilter(arr, transform).flat()
const findDeep = () =>
mapFilterFlat(someObj.level1Items, A =>
mapFilterFlat(A.level2Items, B =>
mapFilter(B.level3Items, C =>
// I am able to access closure variables so i can push them all in my result, instead of just the last level
C == something ? ({A, B, C}) : null
)))
let found: {A: any, B: any, C: any}[] = findDeep();
I am not sure if there are existing Java Stream APIs for accumulate such a result. Maybe it’s not really possible and i should look into another JVM language ?
I eventually did this, but it’s not really concise (although i know Java is not really):
public class Finder implements BiFunction<SomeObj, Predicate<SomeObj>, List<State>> {
static class State {
Integer A;
String B;
List C;
static State from(Map<String, Object> inputs) {
var res = new State();
res.A = (Integer) inputs.get("A");
res.B = (String) inputs.get("B");
res.C = (List) inputs.get("C");
return res;
}
}
Map<String, Object> fields;
<T> T store(String key, T value) {
return (T) fields.put(key, value);
}
public List<State> apply(SomeObj someObj, Predicate<C> predicate) {
fields = new HashMap<>();
return config.level1Items
.stream()
.flatMap(A -> store("A", A).level2Items.stream())
.flatMap(B -> store("B", B).level3Items.stream())
.peek(C -> store("C", C))
.filter(predicate)
.map(o -> State.from(fields))
.collect(Collectors.toList());
}
}
I am not even sure that the BiFunction implementation is useful. Thanks for your guidances
Advertisement
Answer
You are translating TypeScript but you are no translating as it was: you are not “inheriting” the depth of lambda, there are all at the same level and they all don’t see the variable from their parent context.
const findDeep = () =>
mapFilterFlat(someObj.level1Items, A =>
mapFilterFlat(A.level2Items, B =>
mapFilter(B.level3Items, C =>
// I am able to access closure variables so i can push them all in my result, instead of just the last level
C == something ? ({A, B, C}) : null
)))
This is not the same as:
return config.level1Items
.stream()
.flatMap(A -> store("A", A).level2Items.stream())
.flatMap(B -> store("B", B).level3Items.stream())
.peek(C -> store("C", C))
.filter(predicate)
.map(o -> State.from(fields))
.collect(Collectors.toList());
This should be something like this:
return config.level1Items
.stream()
.flatMap(A ->
store("A", A).level2Items
.stream()
.flatMap(B -> store("B", B).level3Items
.stream())
)
.peek(C -> store("C", C)) // the same must be done here
.filter(predicate)
.map(o -> State.from(fields))
.collect(Collectors.toList());
If I try to understand your algorithm, you are trying to get all permutation of {A, B, C} where C = something: your code should be something like this, using forEach to iterate over items of Collection/Iterator.
List<Triple<A,B,C>>> collector = new ArrayList<>();
config.level1Items.forEach(a -> {
a.level2Items.forEach(b -> {
b.level3Items.forEach(c -> {
if (c.equals(something)) {
collector.add(new Triple<>(a, b, c));
}
}
});
});
You don’t need a stream for that.
Triple is simply an implementation of tuple of 3 value, for example the one at commons-lang3.