Converting Map<Long, List<Foo>> to List<Foo> - java

I have a structure like this:
Map<Long, List<Foo>>
where class Foo exposes method:
Class Foo {
public List<Bar> getBars();
public void setBars(List<Bar> bars);
}
Now I want to convert this map to List parametrized with Foo class where each item in this list is Foo instance with aggregated bars list for given long value. For example with map:
{1: [Foo1, Foo2],
2: [Foo3]}
where
Foo1.bars = [Bar1, Bar2]
Foo2.bars = [Bar3]
Foo3.bars = [Bar4, Bar5]
I want to get as a result:
[FooA, FooB]
where
FooA.bars = [Bar1, Bar2, Bar3]
FooB.bars = [Bar4, Bar5]
What would be the most elegant solution for this in Java 8?
Some of the Foo instances from map can be reused if necessary as they are not used anymore after this operation.

Assuming you have a Foo(List<Bar> bars) constructor it's quite easy:
import static java.util.stream.Collectors.*;
List<Foo> result = map.values()
.stream()
.map(
foos -> new Foo(foos.stream()
.flatMap(foo -> foo.getBars().stream())
.collect(toList())))
.collect(toList());
We take the stream of the original map values (we don't care about keys), which are lists of Foo. Each such list we flatten to get the stream of Bar, collect them to list and pass this list to the Foo(List<Bar>) constructor, so we get new Foo objects. Finally we collect them to the List.
If you don't have the Foo(List<Bar>), only setter, you should first create an empty Foo, then use the setter and return the created Foo:
List<Foo> result = map.values()
.stream()
.map(foos -> {
Foo f = new Foo();
f.setBars(foos.stream().flatMap(
foo -> foo.getBars().stream()).collect(toList()));
return f;
})
.collect(toList());
If you don't want to create new Foo objects (for example, there are additional properties you want to keep), it's better to introduce not the setBars, but addBars method (which adds new bars to the existing ones) like this:
public class Foo {
...
public Foo addBars(List<Bar> bars) {
this.bars.addAll(bars);
return this;
}
}
Now you can use the reduce terminal operation to combine the foos:
List<Foo> result = map.values()
.stream()
.map(foos -> foos.stream()
.reduce((foo1, foo2) -> foo1.addBars(foo2.getBars())).get())
.collect(toList());

Related

Sum attributes of an element in a stream [duplicate]

This question already has an answer here:
Sum attribute of object with Stream API
(1 answer)
Closed 2 years ago.
I want to summarize the attributes of my objects.
The objects have a structure like this:
class Foo{
String name;
int = price;
int = fees;
//etc
}
I have collected them into a List and want to get (with streams) a list of all elements that only contain the name and the sum of the price and fees. For example:
{name=Apple, price=2; fees=1}
{name=Potato, price=7, fees=10}
{name=strawberry, price=5, fees=4}
should lead to this result:
{name=Apple, sum=3}
{name=Potato, sum=17}
{name=strawberry, sum=9}
Map<String, Integer> result =
foos.stream()
.collect(
Collectors.groupingBy(
Foo::getName,
Collectors.summingInt(x -> x.getPrice() + x.getFees())
)
);
collect to a Map with a Collectors::groupingBy and a downstream collector : Collectors::summingInt.
Or you can use toMap:
foos.stream()
.collect(
Collectors.toMap(
Foo::getName,
x -> x.getPrice() + x.getFees(),
Integer::sum
)
);
This is rather easy to achieve if you just read the documentation a bit.
Implement class Bar:
class Bar {
String name;
int sum;
// mapping constructor
public Bar(Foo foo) {
this.name = foo.name;
this.sum = foo.price + foo.fees;
}
}
and remap Foo using reference to the constructor:
List<Bar> sums = listFoos.stream().map(Bar::new).collect(Collectors.toList());
Instead of constructor a method with the same functionality may be implemented.
If multiple instances of Foo need to be summarized into single Bar, a method to merge a Bar may be added to Bar:
class Bar {
// ...
public Bar merge(Bar bar) {
this.sum += bar.sum;
return this;
}
}
Then the collection of Bar can be retrieved as values of the following map:
Collection<Bar> sumBars = listFoos
.stream()
.collect(Collectors.toMap(Foo::getName, Bar::new, Bar::merge))
.values();

Aggregate objects into collections in Java using streams

I have a list of objects of structure
public class SimpleObject {
private TypeEnum type;
private String propA;
private Integer propB;
private String propC;
}
which I would like to "pack" into the following objects
public class ComplexObject {
private TypeEnum type;
private List<SimpleObject> simpleObjects;
}
based on TypeEnum.
In other words I'd like to create a sort of aggregations which will hold every SimpleObject that contains a certain type. I thought of doing it with Java 8 streams but I don't know how.
So let's say I'm getting the list of SimpleObjects like
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class DataService {
private final DataRepository dataRepo;
public void getPackedData() {
dataRepo.getSimpleObjects().stream()...
}
}
How the next steps should look like? Thank you in advance for any help
I'm using Java 14 with Spring Boot
You can use Collectors.groupingBy to group by type which return Map<TypeEnum, List<SimpleObject>>. Then again stream over map's entryset to convert into List<ComplexObject>
List<ComplexObject> res =
dataRepo.getSimpleObjects()
.stream()
.collect(Collectors.groupingBy(SimpleObject::getType)) //Map<TypeEnum, List<SimpleObject>>
.entrySet()
.stream()
.map(e -> new ComplexObject(e.getKey(), e.getValue())) // ...Stream<ComplexObject>
.collect(Collectors.toList());
You can achieve this with Collectors.groupingBy and further conversion of entries to a list of objects using Collectors.collectingAndThen.
List<SimpleObject> list = ...
List<ComplexObject> map = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(SimpleObject::getType), // group by TypeEnum
map -> map.entrySet() // map entries to ..
.stream()
.map(e -> new ComplexObject(e.getKey(), e.getValue())) // .. ComplexObject
.collect(Collectors.toList()))); // .. to List
I am not currently aware of another solution as long as Stream API is not friendly in processing dictionary structures.

Split Java-8 Stream result into Success and Failure list

I've a List of Foo, where on each Foo I apply a processor method to get ValidItem.
If there is an error in processing, then I returned ErrorItem.
Now How to process this by Java 8 streams to get the result in form of 2 different lists
List<Foo> FooList = someList....;
class ValidItem extend Item{......}
class ErrorItem extend Item{......}
Item processItem(Foo foo){
return either an object of ValidItem or ErrorItem;
}
I believe I can do this
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(processItem)
.collect(Collectors.groupingBy(Object::getClass));
But as List<Parent> IS NOT a List<Child> so I can't typecast the map result into List<ValidItem>
In reality ErrorItem and ValidItem are two completely different class not related at all, just for the sake of this steam processing and processItem method I kept them in same hierarchy by extending a marker Item class,.
and in many other Places in code, I cant/shouldn't refer ValidItem as Item , as It give an idea that it can be an ErroItem too.
Is there a proper way of doing it with streams, where at the end I get 2 lists. and ErrorItem and
ValidItem are not extending same Item class ?
############## Update ##############
As I said ValidItem and ErrorItem shouldn't be same, so I changed the signature of process method and passed it a list.
I know this is not how Stream shold be used. Let me know if you have better way
List<Foo> FooList = someList....;
class ValidItem {......}
class InvalidFoo{......}
ValidItem processFoo(Foo foo, List<InvalidFoo> foolist){
Do some processing on foo.
either return new ValidItem ();
OR
fooList.add(new InvalidFoo()) , and then return null;
}
List<InvalidFoo> invalidFooList = new ArrayList();
List<ValidItem> validItem =
fooList
.stream()
.map(e->processItem(e,invalidFooList))
.filter(Objects::notNull)
.collect(Collectors.toList());
now I have both invalid and valid list, but this doesn't look like a clean stream code.
With recent Java versions, you can use
for the Item processItem(Foo foo) method returning either ValidItem or ErrorItem:
Map.Entry<List<ValidItem>, List<ErrorItem>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x):null, toList()),
flatMapping(x -> x instanceof ErrorItem? Stream.of((ErrorItem)x):null, toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<ErrorItem> invalid = collected.getValue();
for the ValidItem processFoo(Foo foo, List<InvalidFoo> foolist):
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(foo -> {
List<InvalidFoo> invalid = new ArrayList<>(1);
ValidItem vi = processFoo(foo, invalid);
return new AbstractMap.SimpleImmutableEntry<>(
vi == null? Collections.<ValidItem>emptyList():
Collections.singletonList(vi),
invalid);
})
.collect(teeing(
flatMapping(e -> e.getKey().stream(), toList()),
flatMapping(e -> e.getValue().stream(), toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<InvalidFoo> invalid = collected.getValue();
The flatMapping collector has been introduced in Java 9.
In this specific case, instead of
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x): null, toList())
you can also use
filtering(x -> x instanceof ValidItem, mapping(x -> (ValidItem)x, toList()))
but each variant requires Java 9, as filtering also does not exist in Java 8. The teeing collector even requires Java 12.
However, these collectors are not hard to implement.
This answer contains a Java 8 compatible version of the flatMapping collector at the end. If you want to use the alternative with filtering and mapping, you can find a Java 8 compatible version of filtering in this answer. Finally, this answer contains a Java 8 compatible variant of the teeing collector.
When you add these collectors to your codebase, the solutions at the beginning of this answer work under Java 8 and will being easily adaptable to future versions. Assuming an import static java.util.stream.Collectors.*; in your source file, you only have to remove the backports of these methods, to switch to the standard JDK versions.
It would be better if processItem returned an Either or Pair type instead of the two variants addressed above. If you don’t want to use 3rd party libraries, you can use a Map.Entry instance as a “poor man’s pair type”.
Having a method signature like
/** Returns an entry with either, key or value, being {#code null} */
Map.Entry<ValidItem,InvalidFoo> processItem(Foo foo){
which could be implemented by returning an instance of AbstractMap.SimpleImmutableEntry,
a JDK 9+ solution could look like
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.ofNullable(e.getKey()), toList()),
flatMapping(e -> Stream.ofNullable(e.getValue()), toList()),
AbstractMap.SimpleImmutableEntry::new
));
and Java 8 compatible (when using the linked collector backports) versions:
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.of(e.getKey()).filter(Objects::nonNull), toList()),
flatMapping(e -> Stream.of(e.getValue()).filter(Objects::nonNull), toList()),
AbstractMap.SimpleImmutableEntry::new
));
or
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
mapping(Map.Entry::getKey, filtering(Objects::nonNull, toList())),
mapping(Map.Entry::getValue, filtering(Objects::nonNull, toList())),
AbstractMap.SimpleImmutableEntry::new
));
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Foo> FooList = new ArrayList<>();
for(int i = 0 ; i < 100; i++){
FooList.add(new Foo(i+""));
}
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(Main::processItem)
.collect(Collectors.groupingBy(Object::getClass));
List<ValidItem> validItems = itemsMap.get(ValidItem.class).stream().map((o -> (ValidItem)o)).collect(Collectors.toList());
}
public static Item processItem(Foo foo){
Random random = new Random(System.currentTimeMillis());
if(Integer.parseInt(foo.name) % 2== 0){
return new ValidItem(foo.name);
}else{
return new ErrorItem(foo.name);
}
}
static class ValidItem extends Item{
public ValidItem(String name) {
super("valid: " + name);
}
}
static class ErrorItem extends Item{
public ErrorItem(String name) {
super("error: "+name);
}
}
public static class Item {
private String name;
public Item(String name) {
this.name = name;
}
}
}
I suggest this solution.
You can use Vavr library.
final List<String> list = Arrays.asList("1", ",", "1", "0");
final List<Either<ErrorItem, ValidItem>> eithers = list.stream()
.map(MainClass::processData)
.collect(Collectors.toList());
final List<ValidItem> validItems = eithers.stream()
.filter(Either::isRight)
.map(Either::get)
.collect(Collectors.toList());
final List<ErrorItem> errorItems = eithers.stream()
.filter(Either::isLeft)
.map(Either::getLeft)
.collect(Collectors.toList());
...
private static Either<ErrorItem,ValidItem> processData(String data){
if(data.equals("1")){
return Either.right(new ValidItem());
}
return Either.left(new ErrorItem());
}

Iterate once over list, but return two lists

I have a List<Foo>: foos. And I have two methods that return Bar: Bar doThingOne(Foo foo) and Bar doThingTwo(Foo foo). I want to end up with two List<Bar>s – one by iterating over foos and applying doThingOne() to each Foo, and the other by doing the same thing but with doThingTwo(). Is it possible to do this without iterating over foos twice?
Isn't it what you want? It's so simple that I have some doubt.
List<Bar> lb1 = new ArrayList<Bar>();
List<Bar> lb2 = new ArrayList<Bar>();
for (Foo f : foos) {
lb1.add(doThingOne(f));
lb2.add(doThingTwo(f));
}
You can do this with stream :
List<Bar> barList1 = new ArrayList<>();
List<Bar> barList2 = fooList.stream().peek(f -> barList1.add(doThingOne(f))
.map(f -> doThingTwo(f)).collect(Collectors.toList());

Elegantly combine elements of two lists so that they are unique in a certain property value?

Suppose I have this Java 8 code:
public class Foo {
private long id;
public getId() {
return id;
}
//--snip--
}
//Somewhere else...
List<Foo> listA = getListA();
List<Foo> listB = getListB();
List<Foo> uniqueFoos = ???;
In List<Foo> uniqueFoos I want to add all elements of listA and listB so all Foos have unique IDs. I.e. if there is already a Foo in uniqueFoos that has a particular ID don't add another Foo with the same ID but skip it instead.
Of course there is plain old iteration, but I think there should be something more elegant (probably involving streams, but not mandatory), but I can't quite figure it out...
I can think of good solutions involving an override of the equals() method to basically return id == other.id; and using a Set or distinct(). Unfortunately I can't override equals() because object equality must not change.
What is a clear and efficient way to achieve this?
You can do it with Collectors.toMap:
Collection<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
.collect(Collectors.toMap(
Foo::getId,
f -> f,
(oldFoo, newFoo) -> oldFoo))
.values();
If you need a List instead of a Collection, simply do:
List<Foo> listUniqueFoos = new ArrayList<>(uniqueFoos);
If you also need to preserve encounter order of elements, you can use the overloaded version of Collectors.toMap that accepts a Supplier for the returned map:
Collection<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
.collect(Collectors.toMap(
Foo::getId,
f -> f,
(oldFoo, newFoo) -> oldFoo,
LinkedHashMap::new))
.values();
I think it's worth adding a non-stream variant:
Map<Long, Foo> map = new LinkedHashMap<>();
listA.forEach(f -> map.merge(g.getId(), f, (oldFoo, newFoo) -> oldFoo));
listB.forEach(f -> map.merge(g.getId(), f, (oldFoo, newFoo) -> oldFoo));
Collection<Foo> uniqueFoos = map.values();
This could be refactored into a generic method to not repeat code:
static <T, K> Collection<T> uniqueBy(Function<T, K> groupBy, List<T>... lists) {
Map<K, T> map = new LinkedHashMap<>();
for (List<T> l : lists) {
l.forEach(e -> map.merge(groupBy.apply(e), e, (o, n) -> o));
}
return map.values();
}
Which you can use as follows:
Collection<Foo> uniqueFoos = uniqueBy(Foo::getId, listA, listB);
This approach uses the Map.merge method.
Something like this will do.
List<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
.filter(distinctByKey(Foo::getId))
.collect(Collectors.toList());
public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
You could write this one. This skips the second and next elements that have the same id thanks to filter() and the use of a Set that stores the encountered ids :
Set<Long> ids = new HashSet<>();
List<Foo> uniqueFoos = Stream.concat(getListA().stream(), getListB().stream())
.filter(f -> ids.add(f.getId()))
.collect(Collectors.toList());
It is not a full stream solution but it is rather straight and readable.

Categories