Sum of list size in sub sub stream - java

I have the following structure:
class A {
List<B> bs;
}
class B {
List<C> cs;
}
class C {
List something.
}
I have List of A class and I have to get summ of all elements inside something list. I tried to do the following:
totalCount = as
.stream()
.map(a -> a.getBs()
.stream()
.mapToInt(b -> b.getSomething().size())
.sum());
But that doesn't compile. Where is my mistake?
Compile error is:
Error:(61, 21) java: incompatible types: no instance(s) of type variable(s) R exist so that java.util.stream.Stream<R> conforms to java.lang.Integer

There are many ways to obtain this result, one possible way is to just flat everything and count the results:
A a = ..;
a.bs.stream()
.flatMap(aa -> aa.cs.stream())
.flatMap(bb -> bb.something.stream())
.count();

Let this be a lesson why nested streams are a terrible idea.
You have a mapping function:
Function<A, Integer> func = a -> a.getBs().stream()
.mapToInt(b -> b.getSomething().size())
.sum();
Put this in your initial stream and you get:
totalCount = as
.stream()
.map(func);
Unless totalCount is a Stream<Integer>, then this won't compile.

This is not as short at the other ones, but at least its has test code to verify it works.
public class NewMain {
public static void main(String[] args) {
List<A> as = getAlist();
int totalCount = as
.stream()
.map(a -> a.getBs())
.collect(Collectors.summingInt(bs -> bs.stream()
.map(b -> b.cs)
.collect(Collectors.summingInt(cs -> cs.stream()
.map(c -> c.something)
.collect(Collectors.summingInt(s -> s.size()))))));
System.out.println(totalCount);
}
private static List<A> getAlist() {
List<A> all = new ArrayList<>();
for (int k = 0; k < 10; k++) {
A a = new A();
for (int j = 0; j < 10; j++) {
B b = new B();
for (int i = 0; i < 10; i++) {
C c = new C();
c.something = Arrays.asList(1, 2, 3, 4);
b.cs.add(c);
}
a.bs.add(b);
}
all.add(a);
}
return all;
}
static class A {
List<B> bs = new ArrayList<>();
private List<B> getBs() {
return bs;
}
}
static class B {
List<C> cs = new ArrayList<>();
}
static class C {
List something;
List getSomething() {
return something;
}
}
}

Related

how can I slice my list in chunks before I call a method on each of the chunk?

I have a List of objects in Java:
ArrayList<myObj> objs = generateObjs();
and I have a method responsible for sending the objects further, this method takes the list above as an argument:
sendObjectsFurther(objs)
I want to split the list objs so that I can send further objects in a group of five elements.
What is the best approach to do it?
I thought about implementing something like this:
public void sendSliced(List objs) {
ArrayList<myObj> tempList = new ArrayList()<>;
for (int i = 0; i < objs.size(); i++) {
tempList.add(objs.get(i));
if (i % 5 == 0) {
sendObjectsFurther(tempList);
tempList.clear();
}
}
}
but I think it won't cover all edge cases, could you help me with that? Thanks!
You can use Java 8 Stream API for this.
List<myObj> objs = generateObjs();
int chunkSize = 5;
AtomicInteger counter = new AtomicInteger();
Collection<List<myObj>> result = objs.stream()
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / chunkSize))
.values();
for (myObj chunk: result){
sendObjectsFurther(tempList);
}
Also, class names should start with an upper case.
You could use guava Lists.partition which will split into sub lists.
Example :
for (List<String> slice : Lists.partition(bigList, 5)) {
send(slice);
}
The relevant javadoc link https://guava.dev/releases/23.0/api/docs/com/google/common/collect/Lists.html#partition-java.util.List-int-
You can add one line to your code
public void sendSliced(List objs) {
ArrayList<myObj> tempList = new ArrayList()<>;
for (int i = 0; i < objs.size(); i++) {
tempList.add(objs.get(i));
if (i % 5 == 0) {
sendObjectsFurther(tempList);
tempList.clear();
}
}
sendObjectsFurther(tempList);
}
Which will add remaining items.
Please check the below code, here each sublist will represents a chunk with 5 elements, So suppose I have total of 8 elements in my obj list, so first chunk will have 5 and next chunk will have 3.
public static void main(String[] args) throws IOException {
List<Employee> objs = new ArrayList<>();
objs.add(new Employee(1));
objs.add(new Employee(2));
objs.add(new Employee(3));
objs.add(new Employee(4));
objs.add(new Employee(5));
objs.add(new Employee(6));
objs.add(new Employee(7));
objs.add(new Employee(8));
sendSliced(objs);
}
private static void sendSliced(List<Employee> objs) {
int chunkSize = 5;
final AtomicInteger counter = new AtomicInteger();
Collection<List<Employee>> subLists = objs.stream()
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / chunkSize))
.values();
subLists.stream().forEach( sublist -> System.out.print(sublist));
subLists.stream().forEach( sublist -> sendObjectsFurther(sublist));
}
}
class Employee {
private int id;
Employee(int id) {
this.id = id;
}
}
Ouput Will be something like:
index = 0
[com.practice.stackoverflow.Employee#77459877, com.practice.stackoverflow.Employee#5b2133b1, com.practice.stackoverflow.Employee#72ea2f77, com.practice.stackoverflow.Employee#33c7353a, com.practice.stackoverflow.Employee#681a9515]
index=1
[com.practice.stackoverflow.Employee#3af49f1c, com.practice.stackoverflow.Employee#19469ea2, com.practice.stackoverflow.Employee#13221655]

Java 8 Stream API - Select the lowest key after group by

I have a stream of Foo objects.
class Foo {
private int variableCount;
public Foo(int vars) {
this.variableCount = vars;
}
public Integer getVariableCount() {
return variableCount;
}
}
I want a list of Foo's that all have the lowest variableCount.
For example
new Foo(3), new Foo(3), new Foo(2), new Foo(1), new Foo(1)
I only want the stream to return the last 2 Foos, since they have the lowest value.
I've tried doing a collect with grouping by
.collect(Collectors.groupingBy((Foo foo) -> {
return foo.getVariableCount();
})
And that returns a Map<Integer, List<Foo>> and I'm not sure how to transform that into what I want.
Thanks in advance
You can use a sorted map for grouping and then just get the first entry.
Something along the lines:
Collectors.groupingBy(
Foo::getVariableCount,
TreeMap::new,
Collectors.toList())
.firstEntry()
.getValue()
Here is a solution that:
Only streams the list once.
Doesn't build a map or other structure that contains all of the input items (unless the variable counts are all the same), only keeping those that are currently the minimum.
Is O(n) time, O(n) space. It's entirely possible that all Foos have the same variable count, in which case this solution would store all items like other solutions. But in practice, with different, varied values and higher cardinality, the number of items in the list is likely to be much lower.
Edited
I've improved my solution according to the suggestions in the comments.
I implemented an accumulator object, which supplies functions to the Collector for this.
/**
* Accumulator object to hold the current min
* and the list of Foos that are the min.
*/
class Accumulator {
Integer min;
List<Foo> foos;
Accumulator() {
min = Integer.MAX_VALUE;
foos = new ArrayList<>();
}
void accumulate(Foo f) {
if (f.getVariableCount() != null) {
if (f.getVariableCount() < min) {
min = f.getVariableCount();
foos.clear();
foos.add(f);
} else if (f.getVariableCount() == min) {
foos.add(f);
}
}
}
Accumulator combine(Accumulator other) {
if (min < other.min) {
return this;
}
else if (min > other.min) {
return other;
}
else {
foos.addAll(other.foos);
return this;
}
}
List<Foo> getFoos() { return foos; }
}
Then all we have to do is collect, referencing the accumulator's methods for its functions.
List<Foo> mins = foos.stream().collect(Collector.of(
Accumulator::new,
Accumulator::accumulate,
Accumulator::combine,
Accumulator::getFoos
)
);
Testing with
List<Foo> foos = Arrays.asList(new Foo(3), new Foo(3), new Foo(2), new Foo(1), new Foo(1), new Foo(4));
The output is (with a suitable toString defined on Foo):
[Foo{1}, Foo{1}]
IF you are OK streaming (iterating) twice:
private static List<Foo> mins(List<Foo> foos) {
return foos.stream()
.map(Foo::getVariableCount)
.min(Comparator.naturalOrder())
.map(x -> foos.stream()
.filter(y -> y.getVariableCount() == x)
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
To avoid creating the entire map and also avoiding streaming twice, I copied a custom collector from here https://stackoverflow.com/a/30497254/1264846 and modified it to work with min instead of max. I didn't even know custom collectors were possible so I thank #lexicore for pointing me in that direction.
This is the resulting function minAll
public static <T, A, D> Collector<T, ?, D> minAll(Comparator<? super T> comparator,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BinaryOperator<A> downstreamCombiner = downstream.combiner();
class Container {
A acc;
T obj;
boolean hasAny;
Container(A acc) {
this.acc = acc;
}
}
Supplier<Container> supplier = () -> new Container(downstreamSupplier.get());
BiConsumer<Container, T> accumulator = (acc, t) -> {
if(!acc.hasAny) {
downstreamAccumulator.accept(acc.acc, t);
acc.obj = t;
acc.hasAny = true;
} else {
int cmp = comparator.compare(t, acc.obj);
if (cmp < 0) {
acc.acc = downstreamSupplier.get();
acc.obj = t;
}
if (cmp <= 0)
downstreamAccumulator.accept(acc.acc, t);
}
};
BinaryOperator<Container> combiner = (acc1, acc2) -> {
if (!acc2.hasAny) {
return acc1;
}
if (!acc1.hasAny) {
return acc2;
}
int cmp = comparator.compare(acc1.obj, acc2.obj);
if (cmp < 0) {
return acc1;
}
if (cmp > 0) {
return acc2;
}
acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc);
return acc1;
};
Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc);
return Collector.of(supplier, accumulator, combiner, finisher);
}
You could use collect wisely on the sorted list and in accumulator add the logic to add only either first element to empty list or add any other Foo having variable count same as of the first element of the list.
A complete working example below:-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
class Foo {
private int variableCount;
public Foo(int vars) {
this.variableCount = vars;
}
public Integer getVariableCount() {
return variableCount;
}
public static void main(String[] args) {
List<Foo> list = Arrays.asList(
new Foo(2),
new Foo(2),
new Foo(3),
new Foo(3),
new Foo(1),
new Foo(1)
);
System.out.println(list.stream()
.sorted(Comparator.comparing(Foo::getVariableCount))
.collect(() -> new ArrayList<Foo>(),
(ArrayList<Foo> arrayList, Foo e) -> {
if (arrayList.isEmpty()
|| arrayList.get(0).getVariableCount() == e.getVariableCount()) {
arrayList.add(e);
}
},
(ArrayList<Foo> foos, ArrayList<Foo> foo) -> foos.addAll(foo)
)
);
}
#Override
public String toString() {
return "Foo{" +
"variableCount=" + variableCount +
'}';
}
}
Also, you could first find the minimum variableCount in one stream and use that inside filter of another stream.
list.sort(Comparator.comparing(Foo::getVariableCount));
int min = list.get(0).getVariableCount();
list.stream().filter(foo -> foo.getVariableCount() == min)
.collect(Collectors.toList());
I think in any case either sorting is required or a way to find the minimum number which later can be used inside the predicate. Even if you are using the map to group the values.
Cheers!
Here is alternative with one stream and custom reducer. The idea is to first sort and then collect only elements with first min value:
List<Foo> newlist = list.stream()
.sorted( Comparator.comparing(Foo::getVariableCount) )
.reduce( new ArrayList<>(),
(l, f) -> {
if ( l.isEmpty() || l.get(0).getVariableCount() == f.getVariableCount() ) l.add(f);
return l;
},
(l1, l2) -> {
l1.addAll(l2);
return l1;
}
);
Or using collect is even more compact:
List<Foo> newlist = list.stream()
.sorted( Comparator.comparing(Foo::getVariableCount) )
.collect( ArrayList::new,
(l, f) -> if ( l.isEmpty() || l.get(0).getVariableCount() == f.getVariableCount() ) l.add(f),
List::addAll
);
To avoid creating the map you could use two streams :
the first finds the minimum value.
the second filters elements with this value.
It could give :
List<Foo> foos = ...;
int min = foos.stream()
.mapToInt(Foo::getVariableCount)
.min()
.orElseThrow(RuntimeException::new); // technical error
List<Foo> minFoos = foos.stream()
.filter(f -> f.getVariableCount() == min)
.collect(Collectors.toList());

Perform multiple unrelated operations on elements of a single stream in Java

How can I perform multiple unrelated operations on elements of a single stream?
Say I have a List<String> composed from a text. Each string in the list may or may not contain a certain word, which represents an action to perform. Let's say that:
if the string contains 'of', all the words in that string must be counted
if the string contains 'for', the portion after the first occurrence of 'for' must be returned, yielding a List<String> with all substrings
Of course, I could do something like this:
List<String> strs = ...;
List<Integer> wordsInStr = strs.stream()
.filter(t -> t.contains("of"))
.map(t -> t.split(" ").length)
.collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream()
.filter(t -> t.contains("for"))
.map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
but then the list would be traversed twice, which could result in a performance penalty if strs contained lots of elements.
Is it possible to somehow execute those two operations without traversing twice over the list?
If you want a single pass Stream then you have to use a custom Collector (parallelization possible).
class Splitter {
public List<String> words = new ArrayList<>();
public List<Integer> counts = new ArrayList<>();
public void accept(String s) {
if(s.contains("of")) {
counts.add(s.split(" ").length);
} else if(s.contains("for")) {
words.add(s.substring(s.indexOf("for")));
}
}
public Splitter merge(Splitter other) {
words.addAll(other.words);
counts.addAll(other.counts);
return this;
}
}
Splitter collect = strs.stream().collect(
Collector.of(Splitter::new, Splitter::accept, Splitter::merge)
);
System.out.println(collect.counts);
System.out.println(collect.words);
Here is the answer to address the OP from a different aspect. First of all, let's take a look how fast/slow to iterate a list/collection. Here is the test result on my machine by the below performance test:
When: length of string list = 100, Thread number = 1, loops = 1000, unit = milliseconds
OP: 0.013
Accepted answer: 0.020
By the counter function: 0.010
When: length of string list = 1000_000, Thread number = 1, loops = 100, unit = milliseconds
OP: 99.387
Accepted answer: 89.848
By the counter function: 59.183
Conclusion: The percentage of performance improvement is pretty small or even slower(if the length of string list is small). generally, it's a mistake to reduce the iteration of list/collection which is loaded in memory by the more complicate collector. you won't get much performance improvements. we should look into somewhere else if there is a performance issue.
Here is my performance test code with tool Profiler: (I'm not going to discuss how to do a performance test here. if you doubt the test result, you can do it again with any tool you believe in)
#Test
public void test_46539786() {
final int strsLength = 1000_000;
final int threadNum = 1;
final int loops = 100;
final int rounds = 3;
final List<String> strs = IntStream.range(0, strsLength).mapToObj(i -> i % 2 == 0 ? i + " of " + i : i + " for " + i).toList();
Profiler.run(threadNum, loops, rounds, "OP", () -> {
List<Integer> wordsInStr = strs.stream().filter(t -> t.contains("of")).map(t -> t.split(" ").length).collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream().filter(t -> t.contains("for")).map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
assertTrue(wordsInStr.size() == linePortionAfterFor.size());
}).printResult();
Profiler.run(threadNum, loops, rounds, "Accepted answer", () -> {
Splitter collect = strs.stream().collect(Collector.of(Splitter::new, Splitter::accept, Splitter::merge));
assertTrue(collect.counts.size() == collect.words.size());
}).printResult();
final Function<String, Integer> counter = s -> {
int count = 0;
for (int i = 0, len = s.length(); i < len; i++) {
if (s.charAt(i) == ' ') {
count++;
}
}
return count;
};
Profiler.run(threadNum, loops, rounds, "By the counter function", () -> {
List<Integer> wordsInStr = strs.stream().filter(t -> t.contains("of")).map(counter).collect(Collectors.toList());
List<String> linePortionAfterFor = strs.stream().filter(t -> t.contains("for")).map(t -> t.substring(t.indexOf("for")))
.collect(Collectors.toList());
assertTrue(wordsInStr.size() == linePortionAfterFor.size());
}).printResult();
}
You could use a custom collector for that and iterate only once:
private static <T, R> Collector<String, ?, Pair<List<String>, List<Long>>> multiple() {
class Acc {
List<String> strings = new ArrayList<>();
List<Long> longs = new ArrayList<>();
void add(String elem) {
if (elem.contains("of")) {
long howMany = Arrays.stream(elem.split(" ")).count();
longs.add(howMany);
}
if (elem.contains("for")) {
String result = elem.substring(elem.indexOf("for"));
strings.add(result);
}
}
Acc merge(Acc right) {
longs.addAll(right.longs);
strings.addAll(right.strings);
return this;
}
public Pair<List<String>, List<Long>> finisher() {
return Pair.of(strings, longs);
}
}
return Collector.of(Acc::new, Acc::add, Acc::merge, Acc::finisher);
}
Usage would be:
Pair<List<String>, List<Long>> pair = Stream.of("t of r m", "t of r m", "nice for nice nice again")
.collect(multiple());
If you want to have 1 stream through a list, you need a way to manage 2 different states, you can do this by implementing Consumer to new class.
class WordsInStr implements Consumer<String> {
ArrayList<Integer> list = new ArrayList<>();
#Override
public void accept(String s) {
Stream.of(s).filter(t -> t.contains("of")) //probably would be faster without stream here
.map(t -> t.split(" ").length)
.forEach(list::add);
}
}
class LinePortionAfterFor implements Consumer<String> {
ArrayList<String> list = new ArrayList<>();
#Override
public void accept(String s) {
Stream.of(s) //probably would be faster without stream here
.filter(t -> t.contains("for"))
.map(t -> t.substring(t.indexOf("for")))
.forEach(list::add);
}
}
WordsInStr w = new WordsInStr();
LinePortionAfterFor l = new LinePortionAfterFor();
strs.stream()//stream not needed here
.forEach(w.andThen(l));
System.out.println(w.list);
System.out.println(l.list);

JAVA8 - Grouping with lambda

I have a collection with structure like this:
#Entity
public class RRR{
private Map<XClas, YClas> xySets;
}
and XClas has a field called ZZZ
my question is:
I would like to aggregate it with lambda to get a Map<ZZZ, List<RRR>>.
Is it possible? Now I'm stuck with:
Map xxx = rrrList.stream().collect(
Collectors.groupingBy(x->x.xySets().entrySet().stream().collect(
Collectors.groupingBy(y->y.getKey().getZZZ()))));
but it's Map<Map<ZZZ, List<XClas>>, List<RRR>> so it's not what I was looking for :)
Right now just to make it work, I did aggregation with two nested loops, but it would be so great, if you could help me make it done with lambdas.
EDIT
I post what I got by now, as asked.
I already left nested loops, and I manage to work my way up to this point:
Map<ZZZ, List<RRR>> temp;
rrrList.stream().forEach(x -> x.getxySetsAsList().stream().forEach(z -> {
if (temp.containsKey(z.getKey().getZZZ())){
List<RRR> uuu = new LinkedList<>(temp.get(z.getKey().getZZZ()));
uuu.add(x);
temp.put(z.getKey().getZZZ(), uuu);
} else {
temp.put(z.getKey().getZZZ(), Collections.singletonList(x));
}
}));
Thanks in advance
Something like that? :
Map<ZZZ, List<RRR>> map = new HashMap<>();
list.stream().forEach(rrr -> {
rrr.xySets.keySet().stream().forEach(xclas -> {
if (!map.containsKey(xclas.zzz))
map.put(xclas.zzz, new ArrayList<RRR>());
map.get(xclas.zzz).add(rrr);
});
});
Another way you could do this:
Map<Z, List<R>> map = rs.stream()
.map(r -> r.xys.keySet()
.stream()
.collect(Collectors.<X, Z, R>toMap(x -> x.z, x -> r, (a, b) -> a)))
.map(Map::entrySet)
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Entry::getKey,
Collectors.mapping(Entry::getValue, Collectors.toList())));
I have tried around a bit and found the following solution, posting it here just as another example:
rrrList.stream().map(x -> x.xySets).map(Map::entrySet).flatMap(x -> x.stream())
.collect(Collectors.groupingBy(x -> x.getKey().getZZZ(),
Collectors.mapping(Entry::getValue, Collectors.toList())));
The first line could also be written as rrrList.stream().flatMap(x -> x.xySets.entrySet().stream()) which might be found more readable.
Here is self-contained example code for those wanting to play around themselves:
public static void main(String[] args) {
List<RRR> rrrList = Arrays.asList(new RRR(), new RRR(), new RRR());
System.out.println(rrrList);
Stream<Entry<XClas, YClas>> sf = rrrList.stream().map(x -> x.xySets).map(Map::entrySet).flatMap(x -> x.stream());
Map<ZZZ, List<YClas>> res = sf.collect(Collectors.groupingBy(x -> x.getKey().getZZZ(), Collectors.mapping(Entry::getValue, Collectors.toList())));
System.out.println(res);
}
public static class RRR {
static XClas shared = new XClas();
private Map<XClas, YClas> xySets = new HashMap<>();
RRR() { xySets.put(shared, new YClas()); xySets.put(new XClas(), new YClas()); }
static int s = 0; int n = s++;
public String toString() { return "RRR" + n + "(" + xySets + ")"; }
}
public static class XClas {
private ZZZ zzz = new ZZZ();
public ZZZ getZZZ() { return zzz; }
public String toString() { return "XClas(" + zzz + ")"; }
public boolean equals(Object o) { return (o instanceof XClas) && ((XClas)o).zzz.equals(zzz); }
public int hashCode() { return zzz.hashCode(); }
}
public static class YClas {
static int s = 0; int n = s++;
public String toString() { return "YClas" + n; }
}
public static class ZZZ {
static int s = 0; int n = s++ / 2;
public String toString() { return "ZZZ" + n; }
public boolean equals(Object o) { return (o instanceof ZZZ) && ((ZZZ)o).n == n; }
public int hashCode() { return n; }
}

Clean code - best way to compact code in Java

I have code that looks like this:
for(int i=0; i < a; i++){
List<Integer> list = elementA.get(i);
SomeClass rg = new SomeClass(list, a, methodA(i));
int result = rg.generate();
var+=methodA2(i, result);
}
for(int i=0; i < b; i++){
List<Integer> list = elementB.get(i);
SomeClass rg = new SomeClass(list, b, methodB(i));
int result = rg.generate();
var+=methodB2(i, result);
}
How can I avoid this code repetition? I can create function which does that, but what to do with this different methods?
With Java < 8 you can create an interface (note that there already is an IntFunction interface in Java 8):
interface IntFunction<A> { A apply (int i); }
m(elementA, a, new IntFunction<A> () { public A apply(int i) { methodA(i); } });
And your method would look like:
private void m(Collection<List<Integer>> element, int a, IntFunction<A> f) {
for(int i=0; i < a; i++){
List<Integer> list = element.get(i);
SomeClass rg = new SomeClass(list, a, f.apply(i));
int result = rg.generate();
}
}
(I have omitted the methodA2 for conciseness: you would need a second interface that has an apply(int, int))
That is quite verbose and the benefit is not obvious vs. repetition.
With Java 8 it becomes cleaner:
m(elementA, a, i -> methodA(i));
//or
m(elementA, a, this::methodA);
Define a method that receives your List<List<Integer>> as argument that returns the desired data.
Define an interface that will hold the generic methods like method, method2 (based from your code).
For example:
public long yourFooMethod(List<List<Integer>> listOfData, int n, SomeInterface foo) {
int i = 0;
long var = 0;
for(List<Integer> list : listOfData) {
SomeClass rg = new SomeClass(list, n, foo.method(i));
int result = rg.generate();
var += foo.method2(i, result);
}
return var;
}

Categories