Reputation: 4324
Using Java 8 and working with lists of certain object, using Streams, I need to assign a property to each value of the list depending on properties of other values.
Imagine a class in Java MyClass with these properties:
String value1;
String value2;
LocalDate theDate;
String uniqueId;
And imagine a list of that MyClass objects, just like:
List<MyClass> myList = new ArrayList<>();
The property uniqueId
is empty at the beggining of this exercise.
After mapping the list, the uniqueId
has to be set following this strategy:
<value1>-<value2>-N
where N
is the number of appearances of the <value1>-<value2>
combination sorted by theDate
.
Given this example given in JSON:
[
{"value1":"1111111", "value2":"A", "theDate": "2020-06-01", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-02", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-03", "uniqueId": null},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-04", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-01", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-02", "uniqueId": null},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-03", "uniqueId": null}
]
should result after the execution in:
[
{"value1":"1111111", "value2":"A", "theDate": "2020-06-01", "uniqueId": "1111111-A-1"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-02", "uniqueId": "1111111-A-2"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-03", "uniqueId": "1111111-A-3"},
{"value1":"1111111", "value2":"A", "theDate": "2020-06-04", "uniqueId": "1111111-A-4"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-01", "uniqueId": "1111111-B-1"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-02", "uniqueId": "1111111-B-2"},
{"value1":"1111111", "value2":"B", "theDate": "2020-06-03", "uniqueId": "1111111-B-3"}
]
I clearly could do this using a traditional loop strategy. But the question is, using Streams, how do I manage to keep the track of the rest of the values of the list so I can compose my combined value? Or is it not possible using Streams?
myList.stream()
.map(o -> {
// How do I make here to keep the track of the rest of the values of the list?
})
.collect(Collectors.toList());
Thank you.
Upvotes: 0
Views: 824
Reputation: 7820
This isn't really something were streams shine, and this has two reasons. The first is you are manipulating the MyClass
objects via side-effects (granted, that could be avoided) and because Java lacks out-of-the-box support for something like mapWithIndex
or zipWithIndex
, which would be very helpful here. You can find some more information about how to solve that particlar problem here.
Anyways, it is possible to do via streams.
First things first, I have defined this helper method:
public static MyClass setUniqueId(MyClass myClass, int i) {
myClass.uniqueId = String.format("%s-%s-%s", myClass.value1, myClass.value2, i);
return myClass;
}
It should be fairly self-explanatory - it sets the unique id using the provided int and returns the object itself, now with the id set.
Then, doing what you want is possible by grouping the elements in your stream by equal value1
and value2
values. After grouping the elements, we can set their unique Ids by zipping with their index. As Java lacks support for this, we need to do this in a bit of a backwards way by using an IntStream
with a range that we use to grab indices from. Putting it all together, we get this:
list.stream()
.collect(Collectors.groupingBy(obj -> obj.value1 + "-" + obj.value2))
.forEach((klass, values) -> {
IntStream.range(0, values.size())
.forEach(i -> setUniqueId(values.get(i), i));
});
Another, perhaps more clear approach is to flatMap the result of the grouping:
var results = list.stream()
.collect(Collectors.groupingBy(obj -> obj.value1 + "-" + obj.value2))
.values().stream()
.flatMap(values ->
IntStream.range(0, values.size())
.mapToObj(i -> setUniqueId(values.get(i), i)))
.sorted((a, b) -> a.date.compareTo(b.date))
.collect(Collectors.toList());
Upvotes: 4
Reputation: 17299
You can do like this:
for simplify I just created a constructor with last parameter.
List<MyClass> result = myClasses.stream()
.collect(() -> new HashMap<String, List<MyClass>>(), (m, myClass) ->
m.merge(myClass.getValue2(),
//create new value with the current object
new ArrayList<>(singletonList(new MyClass(myClass.getValue1()
+ "-" + myClass.getValue2() + "-" +
(m.getOrDefault(myClass.getValue2(),
new ArrayList<>()).size() + 1)))),
//merge for duplicate key
(l1, l2) -> {l1.addAll(l2);return l1;}),
HashMap::putAll)
.values().stream().flatMap(List::stream).collect(Collectors.toList());
Here I use HashMap
to store all myclass
object with the same value2
property. when I want to create new object from current myclass
object, consider the map value size for the key(value2
property).for the absent value I've used m.getOrDefault(myClass.getValue2(),new ArrayList<>())
Upvotes: 0