Java 8 stream: sum of units of same items - java-8

Is there a concise way to sum the units of the same type of items in a list with Java 8 streams? For example, suppose I have a list of three items:
{id: 10, units: 1}
{id: 20, units: 2}
{id: 10, units: 1}
I like a summary stream like:
{id: 10, units 2}
{id: 20, units 2}
which sums the units of items of the same id. Any ideas?
Here's Federico's solution (with Lombok):
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class So44348207 {
public static void main(String[] args) {
List<Item> items = Arrays.asList(
new Item(10,1), new Item(20,2), new Item(10, 1)
);
Map<Long, Integer> results = items.stream().collect(
Collectors.toMap(
Item::getId,
Item::getUnits,
Integer::sum
)
);
results.forEach( (k,v) -> System.out.println(
String.format("{id: %d, units: %d}", k,v))
);
}
#Data
#AllArgsContructor
public static class Item {
Long id;
Integer units;
}
}
which correctly produces:
java So44348207
{id: 20, units: 2}
{id: 10, units: 2}

Assuming​ you have a class MyClass that encapsulates both fields and has getters for them, you could do it as follows:
Map<Long, Integer> result = list.stream()
.collect(Collectors.toMap(
MyClass::getId,
MyClass::getUnits,
Integer::sum));
I'm also assuming that id is of type long or Long and units of type int or Integer.
Collectors.toMap creates a map whose keys are determined by its first argument, which is a function that maps an element of the stream to the key of the map. The second argument is a function that maps an element of the stream to the value of the map, and the third argument is an operator that merges two values when there are collisions on the key.

Here is another simple solution by StreamEx
StreamEx.of(items).toMap(Item::getId, Item::getUnits, Integer::sum);

Related

Buffer elements based on its contents with comparer function

In RSJS how to buffer values so buffer will be flushed when next element is different from previous. If elements by some comparator are the same then it should buffer them until next change is detected...
Suppose I have such elements...
{ t: 10, price:12 },
{ t: 10, price:13 },
{ t: 10, price:14 },
{ t: 11, price:12 },
{ t: 11, price:13 },
{ t: 10, price:14 },
{ t: 10, price:15 },
The elements are the same if t property value is the same as previous element t value so at the output I just want such buffers...
[ { t: 10, price:12 }, { t: 10, price:13}, { t: 10, price:14} ],
[ { t: 11, price:12}, { t: 11, price:13} ],
[ { t: 10, price:14 }, { t: 10, price:15 } ]
So in the result I have two elements emited (two buffers each containing the same objects ).
I was trying to use bufferWhen or just buffer but I don't know how to specify closingNotifier in this case because this need to be dependent on elements that are approaching. Anyone can help?
TLDR;
const items = [
{ t: 10, price: 12 },
{ t: 10, price: 13 },
{ t: 10, price: 14 },
{ t: 11, price: 12 },
{ t: 11, price: 13 },
{ t: 10, price: 14 },
{ t: 10, price: 15 }
];
const src$ = from(items).pipe(
delay(0),
share()
);
const closingNotifier$ = src$.pipe(
distinctUntilKeyChanged('t'),
skip(1),
share({ resetOnRefCountZero: false })
);
src$.pipe(bufferWhen(() => closingNotifier$)).subscribe(console.log);
StackBlitz demo.
Detailed explanation
The tricky part was to determine the closingNotifier because, as you said, it depends on the values that come from the stream. My first thought was that src$ has to play 2 different roles: 1) the stream which emits values and 2) the closingNotifier for a buffer operator. This is why the share() operator is used:
const src$ = from(items).pipe(
delay(0),
share()
);
delay(0) is also used because the source's items are emitted synchronously. And since the source would be subscribed twice(because the source is the stream, but also the closingNotifier), its important that both subscribers receive values. If delay(0) was omitted, only the first subscriber would receive the items, and the second one would receive nothing, because it was registered after all the source's items have been emitted. With delay(0) we just ensure that both subscribers(the first one from the subscribe callback and the second one is the inner subscriber of closingNotifier) are registered before the source emits the value.
Onto closingNotifier:
const closingNotifier$ = src$.pipe(
distinctUntilKeyChanged('t'),
skip(1),
share({ resetOnRefCountZero: false })
);
distinctUntilKeyChanged('t'), is used because the signal that the buffer should emit the accumulated items is when an item with a different t value comes from the stream.
skip(1) is used because when the very first value comes from the stream, after the first subscription to the closingNotifier, it will cause the buffered items to be sent immediately, which is not what we want, because it is the first batch of items.
share({ resetOnRefCountZero: false }) - this is the interesting part; as you've seen, we're using bufferWhen(() => closingNotifier$) instead of buffer(closingNotifier$); that is because buffer first subscribes to the source, and then to the notifier; this complicates the situation a bit so I decided to go with bufferWhen, which subscribes to the notifier first and then to the source; the problem with bufferWhen is that it resubscribes the to closingNotifier each time after it emits, so for that we needed to use share, because we wouldn't like to repeat the logic for the first batch of items(the skip operator) when there have already been some items; the problem with share()(without the resetOnRefCountZero option) is that it will still resubscribe each time after it emits, because that's the default behavior when the inner Subject used by share is left without subscribers; this can be solved by using resetOnRefCountZero: false, which won't resubscribe to the source when the first subscriber is registered, after the inner Subject had been previously left without subscribers;

Use RxJs operator like merge but keep track of source observables in the result?

I want to combine observables as is done with the "merge" operator but I still want to be able to know which input observable emitted, is there a way to do that?
For example:
private result$ = merge(this.obs1$, this.obs2$).pipe(
scan((result, change) => index + change, 0),
shareReplay(1)
);
Here both values from obs1 and obs2 will go into the "change" variable in the scan function whenever any of the input observables emit, but if I had access to a projector function where I could mark the values from the input observables with different names I could then do different things in the following scan function depending on which input observable emitted. Other operators like CombineLatest or ForkJoin do not seem to be applicable here either as they require completion or emits from all input observables.
If you need to keep track of which input observable emitted, then you may need to add metadata to your source observable. Without knowing the context of how result$ is used, this is the best possible solution with the information given.
I would suggest adding an id property to each observable you need to keep track of. Then, you can use some strategy in your scan operator based on the ID.
Below is a simple example using an id for each source observable. In the scan operator you will see how my strategy changes based on the ID.
import { interval, merge, of } from "rxjs";
import { map, scan, shareReplay } from "rxjs/operators";
const obs1$ = interval(1000).pipe(map(i => ({ i, id: "obs1" })));
const obs2$ = interval(3000).pipe(map(i => ({ i, id: "obs2" })));
let index = 0;
const result$ = merge(obs1$, obs2$).pipe(
scan((result, change) => {
if (change.id === "obs1") {
return index + change.i;
}
if (change.id === "obs2") {
return index + change.i * 2;
}
}, 0),
shareReplay(1)
);
result$.subscribe(console.log);
https://stackblitz.com/edit/rxjs-as5ket
The library #react-rxjs/utils has a util named mergeWithKey that can be used like this:
import { Subject } from "rxjs"
import { scan, startWith } from 'rxjs/operators'
import { mergeWithKey } from '#react-rxjs/utils'
const inc$ = new Subject()
const dec$ = new Subject()
const resetTo$ = new Subject<number>()
const counter$ = mergeWithKey({
inc$,
dec$,
resetTo$,
}).pipe(
scan((acc, current) => {
switch (current.type) {
case "inc$":
return acc + 1
case "dec$":
return acc - 1
case "resetTo$":
return current.payload
default:
return acc
}
}, 0),
startWith(0),
)
The implementation is pretty straight-forward:
import { merge, Observable, ObservableInput, from, SchedulerLike } from "rxjs"
import { map } from "rxjs/operators"
/**
* Emits the values from all the streams of the provided object, in a result
* which provides the key of the stream of that emission.
*
* #param input object of streams
*/
export const mergeWithKey: <
O extends { [P in keyof any]: ObservableInput<any> },
OT extends {
[K in keyof O]: O[K] extends ObservableInput<infer V>
? { type: K; payload: V }
: unknown
}
>(
x: O,
concurrent?: number,
scheduler?: SchedulerLike,
) => Observable<OT[keyof O]> = (input, ...optionalArgs) =>
merge(
...(Object.entries(input)
.map(
([type, stream]) =>
from(stream).pipe(
map((payload) => ({ type, payload } as any)),
) as any,
)
.concat(optionalArgs) as any[]),
)
Is this what you needed?

Make an ArrayList whose attributes have ArrayList too

I have a problem to make data ArrayList whose attributes have ArrayList too. The model data looks like this code.
data class DataArrayInArray02(
val no: Int? = null,
val dataArray: ArrayList<Int>
)
I want to get a data DataArrayInArray02 like this.
This is my code
fun main() {
val dataArrayInArray = ArrayList<DataArrayInArray02>()
val dataChildrenArray = ArrayList<Int>()
for (i in 0..3) {
val data = (0..10).random()
for (j in 0..data) {
val d = (1..1000).random()
dataChildrenArray.add(d)
}
dataArrayInArray.add(DataArrayInArray02(i+1, dataChildrenArray))
println("ID : ${dataArrayInArray[i].no}, Data : ${dataArrayInArray[i].dataArray}")
dataChildrenArray.clear()
}
}
When I run this code, I get the result like the picture above.
I call dataArrayInArray using looping "for" looks like this.
for (j in 0 until dataArrayInArray.size) {
println("ID : ${dataArrayInArray[j].no}, Data : ${dataArrayInArray[j].dataArray}")
}
But, I get the result like this.
So, which code is incorrect? Is it because of using dataChildrenArray.clear()? If I delete that code. The result will get like this:
ID : 1, Data : [915, 565, 591, 254, 67]
ID : 2, Data : [915, 565, 591, 254, 67, 258, 57, 767, 866, 986, 558, 187, 976]
where ID 2 should only display data [258, 57, 767, 866, 986, 558, 187, 976].
How to solve that problem?
You're passing the same instance of an ArrayList<Int> to each instance of DataArrayInArray02 in the top level list. To fix your code, move the line
val dataChildrenArray = ArrayList<Int>()
inside your for loop so a new one is created for each child.
If you're OK with using List instead of ArrayList, you could simplify this:
val dataArrayInArray = (1..4).map { i ->
val innerListSize = (0..10).random()
val innerList = (0..innerListSize).map { (1..1000).random() }
DataArrayInArray02(i, innerList)
}

Differents subclass instances with same superclass instance data

I'm working with OpenMp and I need to copy an instance of a class N times (one for each thread). I'm using inheritance to create my struct of data. For example:
// Super class
class vehicle {
private:
// general attributes of any vehicle
.......
}
// Sub class
class car : public vehicle {
private:
// some specific attributes of a car
string color;
......
public:
car(string color);
car(car _car);
void change_color(string color);
}
If I have 4 threads, I need to have 4 instance of the car but I would like that this 4 instances shares the same specific data of the super class (only one copy in memory). For example, if I have the follow code:
int main() {
.....
string colors[] = {"green", "blue", "red", "orange"};
.....
car base_car = new car(colors[0]);
car cars[4];
for ( int i = 0; i < 4; i++ ) {
cars[i] = new car(base_car);
}
....
int thread_id;
#pragma omp parallel private (thread_id)
thread_id = omp_get_thread_num();
car thread_car = cars[thread_id];
thread_car.change_color(colors[thread_id]);
}
I'd like that the instances of the car in the cars array share the attributes of the super class vehicle, that is to say, I would like to have only one copy of the data (in memory) of the attributes of the super class vheicle and 4 copy of the data of the derived class car.
This is possible?
Thank you.

How to reduce duplicate elements in stream and compute count of duplicate without intermediate containers

I have a list of objects that can have duplicates. Each object has a frequency attribute. I need to not only count the number of duplicate but also update the frequency for that unique element.
Example
class SomeObject {
private String id;
private String name;
private int frequency;
}
In pre-stream world I can do this
List<SomeObject> listWithDuplicates;
Set<SomeObject> uniques = new HashSet<>();
for(SomeObject someObject: listWithDuplicates) {
if(uniques.contains(someObject)) {
SomeObject seen = uniques.get(someObject);
seen.setFrequency(seen.getFrequency()+1);
} else {
someObject.setFrequency(1);
uniques.add(someObject);
}
}
Question - How can i achieve the same result in java 8 stream without creating an intermediate container?
Your pre-stream code is inconsistent. It seems you can’t decide whether uniques is a Map or a Set. You can’t do get(someObject) on a Set and you can’t do add(someObject) on a Map.
Besides that, it seems to be a common anti-pattern to use an obsolete contains or containsKey pre-check to do a conditional adding or putting despite these operations are already conditional by definition.
In case of a Set, you could do:
Set<SomeObject> uniques = new HashSet<>();
for(SomeObject someObject: listWithDuplicates) {
if(uniques.add(someObject)) someObject.setFrequency(1);
else someObject.setFrequency(someObject.getFrequency()+1);
}
or even simpler
Set<SomeObject> uniques = new HashSet<>();
for(SomeObject someObject: listWithDuplicates) {
someObject.setFrequency(uniques.add(someObject)? 1: someObject.getFrequency()+1);
}
Of course, this assumes that equality implies identity, i.e. that SomeObject doesn’t have an equals implementation that allows distinct instances to be considered equal.
Otherwise, using a Map to get the canonical, i.e. first encountered instance is unavoidable:
Map<SomeObject, SomeObject> map = new HashMap<>();
for(SomeObject someObject: listWithDuplicates) {
SomeObject previous = map.putIfAbsent(someObject, someObject);
if(previous==null) someObject.setFrequency(1);
else previous.setFrequency(previous.getFrequency()+1);
}
Set<SomeObject> uniques = map.keySet();
One way to achieve the same with a Stream is
Set<SomeObject> uniques=listWithDuplicates.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(Function.identity(), Collectors.summingInt(x -> 1)),
map -> {
map.forEach(SomeObject::setFrequency);
return map.keySet();
}));
First you will always create an intermediate container with or without streams, where are you going to store the intermediate result otherwise?
From a stream perspective I came up with this:
Set<SomeObject> set = list.stream().collect(Collectors.toMap(s -> {
s.setFrequency(1);
return s;
}, s -> s, (l, r) -> {
l.setFrequency(l.getFrequency() + r.getFrequency());
return l;
})).keySet();

Resources