How to view intermediate results in a stream? - java

How can I print out the result of the .collect() while it is processing?
When I try a forEach, I get a compiler error.
filter and peek only apply to streams.
var landmarks = Set.of("Eiffel Tower", "Statue of Liberty",
"Stonehenge", "Mount Fuji");
var result = landmarks
.stream()
.collect(Collectors.partitioningBy(b -> b.contains(" ")))
<---------------what to put here?
.entrySet()
.stream()
.flatMap(t -> t.getValue().stream())
.collect(Collectors.groupingBy(s -> !s.startsWith("S")));

If you are using IntelliJ, you can use the Trace Current Strean Chain functionality while debugging.

Unfortunately, it's impossible to achieve what you're asking in a concise way, since Java doesn't support extension or postfix functions. So, there is no way to add new method (e.g. .debugPrint()) into Map (the result of .collect).
( If you'd asked this question for Kotlin, the answer would've been to use .also{}. )
However, there are couple of tricks that come close.
1. The easy way
Just wrap the whole chain in the function that prints and returns the result.
Pros: easy to write
Cons: breaks the nice formatting of the chain
var result = printed(
landmarks
.stream()
.collect(Collectors.partitioningBy(b -> b.contains(" ")))
)
.entrySet()
.stream()
.flatMap(t -> t.getValue().stream())
.collect(Collectors.groupingBy(s -> !s.startsWith("S")));
// ...
// required helper function
static <T> T printed(T any) {
System.out.println(any);
return any;
}
2. The verbose way
You can create a function that wraps the Collector and prints it's result after it's finished collecting.
Pros: stream chain still looks nice
Cons: collector wrapper implementation is pretty verbose
var result = landmarks
.stream()
.collect(printed(Collectors.partitioningBy(b -> b.contains(" "))))
.entrySet()
.stream()
.flatMap(t -> t.getValue().stream())
.collect(Collectors.groupingBy(s -> !s.startsWith("S")));
// ...
// The required helper function
static <T, A, R> Collector<T, A, R> printed(Collector<T, A, R> wrapped) {
return new Collector<T, A, R>() {
#Override
public Supplier<A> supplier() {
return wrapped.supplier();
}
#Override
public BiConsumer<A, T> accumulator() {
return wrapped.accumulator();
}
#Override
public BinaryOperator<A> combiner() {
return wrapped.combiner();
}
#Override
public Function<A, R> finisher() {
return (A x) -> {
R res = wrapped.finisher().apply(x);
System.out.println(res);
return res;
};
}
#Override
public Set<Characteristics> characteristics() {
// Need to ensure IDENTITY_FINISH is removed, otherwise `finisher` is not called
return wrapped.characteristics().stream().filter(c -> c != IDENTITY_FINISH).collect(Collectors.toSet());
}
};
}
Both approaches print (in java 15):
{false=[Stonehenge], true=[Mount Fuji, Eiffel Tower, Statue of Liberty]}

You can add a dummy stage to the chain with a side-effect:
public class Example {
public static void main(String[] args) {
var landmarks = Set.of("Eiffel Tower", "Statue of Liberty",
"Stonehenge", "Mount Fuji");
var result = landmarks
.stream()
.collect(Collectors.partitioningBy(b -> b.contains(" ")))
.entrySet()
.stream()
.map( e -> {
System.out.println(e.getKey() + ": " + e.getValue());
return e;
})
.flatMap(t -> t.getValue().stream())
.collect(Collectors.groupingBy(s -> !s.startsWith("S")));
}
}

I am not sure if I fully understood the problem. Is this what yout are trying to achieve?
void test() {
var landmarks = Set.of("Eiffel Tower", "Statue of Liberty", "Stonehenge", "Mount Fuji");
var result1 = landmarks
.stream()
.collect(Collectors.partitioningBy(this::containsSpace))
.entrySet()
.stream()
.flatMap(t -> t.getValue().stream())
.collect(Collectors.groupingBy(s -> !s.startsWith("S")));
}
private boolean containsSpace(String in) {
var contains = in.contains(" ");
System.out.printf("%b - %s%n", contains, in);
return contains;
}
Result:
true - Statue of Liberty
true - Mount Fuji
true - Eiffel Tower
false - Stonehenge

Related

Bad return type in lambda expression:

List<CategoryWiseEarnings> data = tripEarnings.getOrders()
.stream()
.flatMap(getCategoryRulesEarnedList -> getCategoryRulesEarnedList.getCategoryRulesEarnedList().stream())
.collect(Collectors.groupingBy(foo -> foo.getCategoryId()))
.entrySet()
.stream()
.map(e -> e.getValue()
.stream()
.reduce((c,c2) -> new CategoryWiseEarnings(
new CategoryWise(
c.getCategoryName(),
c.getCategoryId()
),
c.getBonus()
))
)
.map(f -> f.get())
.collect(Collectors.toList());
Getting Exception as
:Bad return type in lambda expression: CategoryWiseEarnings cannot be converted to CategoryWise
public class CategoryWiseEarnings {
#JsonProperty("category")
private CategoryWise earnings;
#JsonProperty("total_amount")
private String totalAmount;
}
public class CategoryWise {
#JsonProperty("category_id")
Long categoryId;
#JsonProperty("category_name")
String categoryName;
public CategoryWise(String categoryName, Long categoryId) {
this.categoryName = categoryName;
this.categoryId = categoryId;
}
}
This is my code which I want to write using streams and lambda function it is working fine if I write like this:
for (Trips tripsOrders : tripEarnings.getOrders()) {
if (!tripsOrders.getCategoryRulesEarnedList().isEmpty()) {
for (CategoryWise c : tripsOrders.getCategoryRulesEarnedList()) {
if (hashMapCategory.containsKey(c.getCategoryId())) {
// hashmapk.put(c.getCategoryId(),new CategoryWiseEarnings(new CategoryWise(c.getCategoryName(),c.getCategoryId()),c.getBonus()+hashmapk.get(c.getCategoryId()).getTotalAmount()));
CategoryWiseEarnings categoryObject = hashMapCategory.get(c.getCategoryId());
categoryObject.setTotalAmount(Double.toString(
Double.parseDouble(c.getBonus())
+ Double.parseDouble(categoryObject.getTotalAmount())
));
hashMapCategory.put(c.getCategoryId(), categoryObject);
} else {
hashMapCategory.put(c.getCategoryId(), new CategoryWiseEarnings(new CategoryWise(c.getCategoryName(), c.getCategoryId()), c.getBonus()));
}
}
}
}
List<CategoryWiseEarnings> list = new ArrayList<CategoryWiseEarnings>(hashMapCategory.values());
Stream::reduce(BinaryOperator) expects a BiFunction<T, T, T> while in the OP's code this contract is broken: (CategoryWise c, CategoryWiseEarnings c2) -> new CategoryWiseEarnings()
Also, the data model seems to be improper: in CategoryWiseEarnings field total should be Double to avoid redundant conversions, class CategoryWise is missing bonus field.
So, the solution could be to use Collectors.groupingBy and Collectors.summingDouble together to calculate total values in a map, and then re-map the map entries into CategoryWiseEarnings:
List<CategoryWiseEarnings> result = tripEarnings.getOrders()
.stream() // Stream<Trips>
.flatMap(trips -> trips.getCategoryRulesEarnedList().stream()) // Stream<CategoryWise>
.collect(Collectors.groupingBy(
cw -> Arrays.asList(cw.getCategoryId(), cw.getCategoryName()) // key -> List<Object>
LinkedHashMap::new, // keep insertion order
Collectors.summingDouble(cw -> Double.parseDouble(cw.getBonus()))
)) // Map<List<Id, Name>, Double>
.entrySet()
.stream()
.map(e -> new CategoryWiseEarnings(
new CategoryWise(e.getKey().get(0), e.getKey().get(1)),
String.valueOf(e.getValue()) // assuming the total is String
))
.collect(Collectors.toList());

how to keep track of mulitple closure variable in Java lambdas (or JVM language)?

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
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.
Based on NoDataFound's answer I eventually did this:
var collector = AA.stream()
.flatMap(A -> A.BB.stream()
.flatMap(B -> B.CC.stream()
.filter(predicateOnC)
.map(C -> Triple.of(A, B, C))))
.collect(Collectors.toList());

Find an element over an Iterable

I have the following class:
public static class GenerateMetaAlert implements WindowFunction<Tuple2<String, Boolean>, Tuple2<String, Boolean>, Tuple, TimeWindow> {
#Override
public void apply(Tuple key, TimeWindow timeWindow, Iterable<Tuple2<String, Boolean>> iterable, Collector<Tuple2<String, Boolean>> collector) throws Exception {
//code
}
}
What I'm trying to do is is for each element of the collection there are any other with the opposite value in a field.
An example:
Iterable: [<val1,val2>,<val3,val4>,<val5,val6>,...,<valx,valy>]
|| || || ||
elem1 elem2 elem3 elemn
What I would like to test:
foreach(element)
if elem(i).f0 = elem(i+1).f0 then ...
if elem(i).f0 = elem(i+2).f0 then ...
<...>
if elem(i+1).f0 = elem(i+2).f0 then ...
<...>
if elem(n-1).f0 = elem(n).f0 then ...
I think this would be possible using something like this:
Tuple2<String, Boolean> tupla = iterable.iterator().next();
iterable.iterator().forEachRemaining((e)->{
if ((e.f0 == tupla.f0) && (e.f1 != tupla.f1)) collector.collect(e);});
But like i'm new with Java, I don't know how I could do it in an optimal way.
This is a part of a Java program which use Apache Flink:
.keyBy(0, 1)
.timeWindow(Time.seconds(60))
.apply(new GenerateMetaAlert())
Testing:
Using the following code:
public static class GenerateMetaAlert implements WindowFunction<Tuple2<String, Boolean>, Tuple2<String, Boolean>, Tuple, TimeWindow> {
#Override
public void apply(Tuple key, TimeWindow timeWindow, Iterable<Tuple2<String, Boolean>> iterable, Collector<Tuple2<String, Boolean>> collector) throws Exception {
System.out.println("key: " +key);
StreamSupport.stream(iterable.spliterator(), false)
.collect(Collectors.groupingBy(t -> t.f0)) // yields a Map<String, List<Tuple2<String, Boolean>>>
.values() // yields a Collection<List<Tuple2<String, Boolean>>>
.stream()
.forEach(l -> {
System.out.println("l.size: " +l.size());
// l is the list of tuples for some common f0
while (l.size() > 1) {
Tuple2<String, Boolean> t0 = l.get(0);
System.out.println("t0: " +t0);
l = l.subList(1, l.size());
l.stream()
.filter(t -> t.f1 != t0.f1)
.forEach(t -> System.out.println("t: "+ t));
}
});
}
}
The result is:
key: (868789022645948,true)
key: (868789022645948,false)
l.size: 2
l.size: 2
t0: (868789022645948,true)
t0: (868789022645948,false)
Conclusion of this test: is like the condition .filter(t -> t.f1 != t0.f1) is never met
If I change .filter(t -> t.f1 != t0.f1) for .filter(t -> t.f1 != true) (or false) the filter works
I also use the following:
final Boolean[] aux = new Boolean[1];
<...>
Tuple2<String, Boolean> t0 = l.get(0);
aux[0] = t0.f1;
<...>
.filter(t -> !t.f1.equals(aux[0]))
But even with that, I don't have any output (I only have it when I use t.f1.equals(aux[0])
An Iterable allows you to obtain as many Iterators over its elements as you like, but each of them iterates over all the elements, and only once. Thus, your idea for using forEachRemaining() will not work as you hope. Because you're generating a new Iterator to invoke that method on, it will start at the beginning instead of after the element most recently provided by the other iterator.
What you can do instead is create a Stream by use of the Iterable's Spliterator, and use a grouping-by Collector to group the iterable's tuples by their first value. You can then process the tuple lists as you like.
For example, although I have some doubts as to whether it's what you actually want, this implements the logic described in the question:
StreamSupport.stream(iterable.spliterator(), false)
.collect(Collectors.groupingBy(t -> t.f0)) // yields a Map<String, List<Tuple2<String, Boolean>>>
.values() // yields a Collection<List<Tuple2<String, Boolean>>>
.stream()
.forEach(l -> {
// l is the list of tuples for some common f0
while (l.size() > 1) {
Tuple2<String, Boolean> t0 = l.get(0);
l = l.subList(1, l.size());
l.stream()
.filter(t -> t.f1 != t0.f1)
.forEach(t -> collect(t));
}
});
Note well that that can collect the same tuple multiple times, as follows from your pseudocode. If you wanted something different, such as collecting only tuples representing a flip of f1 value for a given f0, once each, then you would want a different implementation of the lambda in the outer forEach() operation.

Group objects by multiple attributes with Java 8 stream API

Given we have a list of Bank, each Bank have multiple offices,
public class Bank {
private String name;
private List<String> branches;
public String getName(){
return name;
}
public List<String> getBranches(){
return branches;
}
}
For example:
Bank "Mizuho": branches=["London", "New York"]
Bank "Goldman": branches = ["London", "Toronto"]
Given a list of banks, I would have a map of bank representation for each city. In the example above, I need a result of
Map["London"] == ["Mizuho", "Goldman"]
Map["New York"] == ["Mizuho"]
Map["Toronto"] == ["Goldman"]
How can I achieve that result using Java 8 API? Using pre-Java8 is easy, but verbose.
Thank you.
Map<String, Set<Bank>> result = new HashMap<>();
for (Bank bank : banks) {
for (String branch : bank.getBranches()) {
result.computeIfAbsent(branch, b -> new HashSet<Bank>()).add(bank);
}
}
banks.flatMap(bank -> bank.getBranches()
.stream()
.map(branch -> new AbstractMap.SimpleEntry<>(branch, bank)))
.collect(Collectors.groupingBy(
Entry::getKey,
Collectors.mapping(Entry::getValue, Collectors.toList())));
Result would be:
{London=[Mizuho, Goldman], NewYork=[Mizuho], Toronto=[Goldman]}
You could do it using the version of Stream.collect that accepts a supplier, an accumulator function and a combiner function, as follows:
Map<String, List<Bank>> result = banks.stream()
.collect(
HashMap::new,
(map, bank) -> bank.getBranches().forEach(branch ->
map.computeIfAbsent(branch, k -> new ArrayList<>()).add(bank)),
(map1, map2) -> map2.forEach((k, v) -> map1.merge(k, v, (l1, l2) -> {
l1.addAll(l2);
return l1;
})));
I think solution provided by #JB Nizet is one of the most simple/efficient solutions. it can also be rewritten by forEach
banks.forEach(b -> b.getBranches().forEach(ch -> result.computeIfAbsent(ch, k -> new ArrayList<>()).add(b)));
Another short solution by Stream with abacus-common
Map<String, List<Bank>> res = Stream.of(banks)
.flatMap(e -> Stream.of(e.getBranches()).map(b -> Pair.of(b, e)))
.collect(Collectors.toMap2());

Converting a Map to another Map using Stream API

I have a Map<Long, List<Member>>() and I want to produce a Map<Member, Long> that is calculated by iterating that List<Member> in Map.Entry<Long, List<Member>> and summing the keys of each map entry for each member in that member list. It's easy without non-functional way but I couldn't find a way without writing a custom collector using Java 8 Stream API. I think I need something like Stream.collect(Collectors.toFlatMap) however there is no such method in Collectors.
The best way that I could found is like this:
longListOfMemberMap = new HashMap<Long, List<Member>>()
longListOfMemberMap.put(10, asList(member1, member2));
Map<Member, Long> collect = longListOfMemberMap.entrySet().stream()
.collect(new Collector<Map.Entry<Long, List<Member>>, Map<Member, Long>, Map<Member, Long>>() {
#Override
public Supplier<Map<Member, Long>> supplier() {
return HashMap::new;
}
#Override
public BiConsumer<Map<Member, Long>, Map.Entry<Long, List<Member>>> accumulator() {
return (memberLongMap, tokenRangeListEntry) -> tokenRangeListEntry.getValue().forEach(member -> {
memberLongMap.compute(member, new BiFunction<Member, Long, Long>() {
#Override
public Long apply(Member member, Long aLong) {
return (aLong == null ? 0 : aLong) + tokenRangeListEntry.getKey();
}
});
});
}
#Override
public BinaryOperator<Map<Member, Long>> combiner() {
return (memberLongMap, memberLongMap2) -> {
memberLongMap.forEach((member, value) -> memberLongMap2.compute(member, new BiFunction<Member, Long, Long>() {
#Override
public Long apply(Member member, Long aLong) {
return aLong + value;
}
}));
return memberLongMap2;
};
}
#Override
public Function<Map<Member, Long>, Map<Member, Long>> finisher() {
return memberLongMap -> memberLongMap;
}
#Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
});
// collect is equal to
// 1. member1 -> 10
// 2. member2 -> 10
The code in the example takes a Map> as parameter and produces a Map:
parameter Map<Long, List<Member>>:
// 1. 10 -> list(member1, member2)
collected value Map<Member, Long>:
// 1. member1 -> 10
// 2. member2 -> 10
However as you see it's much more ugly than the non-functional way. I tried Collectors.toMap and reduce method of Stream but I couldn't find a way to do with a few lines of code.
Which way would be the simplest and functional for this problem?
longListOfMemberMap.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().map(
member ->
new AbstractMap.SimpleImmutableEntry<>(member, entry.getKey())))
.collect(Collectors.groupingBy(
Entry::getKey,
Collectors.summingLong(Entry::getValue)));
...though an even simpler but more imperative alternative might look like
Map<Member, Long> result = new HashMap<>();
longListOfMemberMap.forEach((val, members) ->
members.forEach(member -> result.merge(member, val, Long::sum)));
I will just point out that the code you have posted can be written down much more concisely when relying on Collector.of and turning your anonymous classes into lambdas:
Map<Member, Long> result = longListOfMemberMap.entrySet().stream()
.collect(Collector.of(
HashMap::new,
(acc, item) -> item.getValue().forEach(member -> acc.compute(member,
(x, val) -> Optional.ofNullable(val).orElse(0L) + item.getKey())),
(m1, m2) -> {
m1.forEach((member, val1) -> m2.compute(member, (x, val2) -> val1 + val2));
return m2;
}
));
This still cumbersome, but at least not overwhelmingly so.

Categories

Resources