Map<A, List<B>> xFunction() {
Map<A, List<B>> mapList = new HashMap<>();
List<A> aList = x.getAList();
for (A a : aList) {
List<B> bList = getBList(a.getId());
mapList.put(a, bList);
}
return mapList;
}
How to convert this in java 8 with collect and grouping by or mapping?
I try with something like:
x.getAList
.stream()
.map(a -> getBList(a.getId)) //return a list of B
.collect(Collectors.groupingBy (...) )
Cheers
You need Collectors.toMap:
Map<A, List<B>> map =
x.getAList()
.stream()
.collect(Collectors.toMap (Function.identity(), a -> getBList(a.getId())));
#Eran was first but to reproduce behavior you should use toMap collector with mergeFunction for duplicates by a.getId() because by default Java will throw IllegalStateException for entries with the same key:
x.getAList()
.stream()
.collect(Collectors.toMap(Function.identity(), a -> getBList(a.getId())), (u, u2) -> u2);
Related
Input: List<Foo> rawdata
Desired Output: Map<Foo, Bar>
Method implementation: Java 8
public Map<Foo, Bar> getMap(List<Foo> rawdata) {
rawData.stream().flatmap(d -> {
val key = method1(d);
val value = method2(d);
})
// Now I want to create a map using the Key/Value
// pair which was obtained from calling method 1 and 2.
}
I did try this:
public Map<Foo, Bar> getMap(List<Foo> rawdata) {
rawData.stream()
.flatmap(
d -> {
val key = method1(d);
val value = method2(d);
return Stream.of(new Object[][]{key, value});
})
.collect(Collectors.toMap(
key -> key[0],
key -> key[1]));
}
However want to see if there is less expensive way of doing this or any better way.
There's no point in creating an intermediate stream and then using flatMap().
You can just collect() directly into a map:
public Map<Foo, Bar> getMap(List<Foo> rawData) {
return rawData.stream()
.collect(Collectors.toMap(foo -> method1(foo), foo -> method2(foo)));
// OR: Collectors.toMap(this::method1, this::method2);
}
You can use toMap(keyMapper,valueMapper,mergeFunction) method with three parameters that allows to merge duplicates into a list, for example:
List<String[]> list = List.of(
new String[]{"Foo1", "Bar1"},
new String[]{"Foo1", "Bar2"},
new String[]{"Foo2", "Baz"});
Map<String, List<String>> map1 = list.stream()
.collect(Collectors.toMap(
e -> e[0],
e -> new ArrayList<>(List.of(e[1])),
(list1, list2) -> {
list1.addAll(list2);
return list1;
}));
System.out.println(map1);
// {Foo1=[Bar1, Bar2], Foo2=[Baz]}
If you are sure that there are no duplicates, you can use toMap(keyMapper,valueMapper) method with two parameters:
List<String[]> list = List.of(
new String[]{"Foo1", "Bar1"},
new String[]{"Foo2", "Baz"});
Map<String, String> map2 = list.stream()
.collect(Collectors.toMap(e -> e[0], e -> e[1]));
System.out.println(map2);
// {Foo1=Bar1, Foo2=Baz}
Anyway you can collect a list of Map.Entrys:
List<String[]> list = List.of(
new String[]{"Foo1", "Bar1"},
new String[]{"Foo1", "Bar2"},
new String[]{"Foo2", "Baz"});
List<Map.Entry<String, String>> list1 = list.stream()
.map(arr -> Map.entry(arr[0], arr[1]))
.collect(Collectors.toList());
System.out.println(list1);
// [Foo1=Bar1, Foo1=Bar2, Foo2=Baz]
See also: Ordering Map<String, Integer> by List<String> using streams
Assuming I have the following classes:
class A {
int id;
List<B> b;
}
class B {
int id;
}
I want to create a map between A.id to the list of B.id (Map<Integer, List<Integer>> , where key = A.id, and List<Integer> corresponds to the list of B.id fields for each A ). I tried various combinations of Collectors.groupingBy and Collectors.mapping, but to no effect. Can somebody help me out ?
You may use the toMap collector with a merge function to solve this problem. Here's how it looks.
Map<Integer, List<Integer>> resultMap = aList.stream()
.collect(Collectors.toMap(A::getId,
a -> a.getB().stream().map(B::getId)
.collect(Collectors.toList()), (l1, l2) -> {
l1.addAll(l2);
return l1;
}));
However, if a given set of A objects have distinct id values, then you can merely dispense with the merge function, which is the third argument to the toMap collector. Here's the simplified version.
Map<Integer, List<Integer>> resultMap = aList.stream()
.collect(Collectors.toMap(A::getId,
a -> a.getB().stream().map(B::getId)
.collect(Collectors.toList())));
List<A> aList;
//initialize aList here...
Map<String, List<Integer>> aMap = aList.stream().collect(
Collectors.toMap(element -> element.id,
element -> element.b
.stream()
.map(b -> b.id)
.collect(Collectors.toList())
)
);
You can do it in java-8 using Collectors.mapping to only map List<B> b while groping and then Collectors.collectingAndThen for the final transformation of value List<List<B>> to List<Integer>
Map<Integer, List<Integer>> result = list.stream()
.collect(Collectors.groupingBy(A::getId,
Collectors.mapping(A::getB, Collectors.collectingAndThen(Collectors.toList(),
l -> l.stream().flatMap(List::stream).map(B::getId).collect(Collectors.toList())))));
Java 9 added flatMapping collector, which is perfect for this:
// using static imports of various Collectors
listOfA.stream().collect(
groupingBy(
A::getId,
flatMapping(
a -> a.getB().stream().map(B::getId),
toList()
)
)
);
I have List<Person> persons = new ArrayList<>(); and I want to list all unique names. I mean If there are "John", "Max", "John", "Greg" then I want to list only "Max" and "Greg". Is there some way to do it with Java stream?
We can use streams and Collectors.groupingBy in order to count how many occurrences we have of each name - then filter any name that appears more than once:
List<String> res = persons.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(e -> e.getValue() == 1)
.map(e -> e.getKey())
.collect(Collectors.toList());
System.out.println(res); // [Max, Greg]
List persons = new ArrayList();
persons.add("Max");
persons.add("John");
persons.add("John");
persons.add("Greg");
persons.stream()
.filter(person -> Collections.frequency(persons, person) == 1)
.collect(Collectors.toList());
First guess solution.
persons.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(entry -> entry.getValue() == 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList())
Here is my solution:
List<String> persons = new ArrayList<>();
persons.add("John");
persons.add("John");
persons.add("MAX");
persons.add("Greg");
persons.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
This is an old post, but I'd like to propose yet another approach based on a custom collector:
public static <T> Collector<T, ?, List<T>> excludingDuplicates() {
return Collector.<T, Map<T, Boolean>, List<T>>of(
LinkedHashMap::new,
(map, elem) -> map.compute(elem, (k, v) -> v == null),
(left, right) -> {
right.forEach((k, v) -> left.merge(k, v, (o, n) -> false));
return left;
},
m -> m.keySet().stream().filter(m::get).collect(Collectors.toList()));
}
Here I'm using Collector.of to create a custom collector that will accumulate elements on a LinkedHashMap: if the element is not present as a key, its value will be true, otherwise it will be false. The merge function is only applied for parallel streams and it merges the right map into the left map by attempting to put each entry of the right map in the left map, changing the value of already present keys to false. Finally, the finisher function returns a list with the keys of the map whose values are true.
This method can be used as follows:
List<String> people = Arrays.asList("John", "Max", "John", "Greg");
List<String> result = people.stream().collect(excludingDuplicates());
System.out.println(result); // [Max, Greg]
And here's another approach simpler than using a custom collector:
Map<String, Boolean> duplicates = new LinkedHashMap<>();
people.forEach(elem -> duplicates.compute(elem, (k, v) -> v != null));
duplicates.values().removeIf(v -> v);
Set<String> allUnique = duplicates.keySet();
System.out.println(allUnique); // [Max, Greg]
You can try the below code.
List<Person> uniquePersons = personList.stream()
.collect(Collectors.groupingBy(person -> person.getName()))
.entrySet().stream().filter(stringListEntry -> stringListEntry.getValue().size()==1)
.map(stringListEntry -> { return stringListEntry.getValue().get(0); })
.collect(Collectors.toList());
This should remove all the duplicate elements.
List<String> persons = new ArrayList<>();
persons.add("John");
persons.add("John");
persons.add("MAX");
persons.add("Greg");
Set<String> set = new HashSet<String>();
Set<String> duplicateSet = new HashSet<String>();
for (String p : persons) {
if (!set.add(p)) {
duplicateSet.add(p);
}
}
System.out.println(duplicateSet.toString());
set.removeAll(duplicateSet);
System.out.println(set.toString());
You can simply use Collections.frequency to check the element occurance in the list as shown below to filter the duplicates:
List<String> listInputs = new ArrayList<>();
//add your users
List<String> listOutputs = new ArrayList<>();
for(String value : listInputs) {
if(Collections.frequency(listInputs, value) ==1) {
listOutputs.add(value);
}
}
System.out.println(listOutputs);
How could I do the following with Java Streams?
Let's say I have the following classes:
class Foo {
Bar b;
}
class Bar {
String id;
String date;
}
I have a List<Foo> and I want to convert it to a Map <Foo.b.id, Map<Foo.b.date, Foo>. I.e: group first by the Foo.b.id and then by Foo.b.date.
I'm struggling with the following 2-step approach, but the second one doesn't even compile:
Map<String, List<Foo>> groupById =
myList
.stream()
.collect(
Collectors.groupingBy(
foo -> foo.getBar().getId()
)
);
Map<String, Map<String, Foo>> output = groupById.entrySet()
.stream()
.map(
entry -> entry.getKey(),
entry -> entry.getValue()
.stream()
.collect(
Collectors.groupingBy(
bar -> bar.getDate()
)
)
);
Thanks in advance.
You can group your data in one go assuming there are only distinct Foo:
Map<String, Map<String, Foo>> map = list.stream()
.collect(Collectors.groupingBy(f -> f.b.id,
Collectors.toMap(f -> f.b.date, Function.identity())));
Saving some characters by using static imports:
Map<String, Map<String, Foo>> map = list.stream()
.collect(groupingBy(f -> f.b.id, toMap(f -> f.b.date, identity())));
Suppose (b.id, b.date) pairs are distinct. If so,
in second step you don't need grouping, just collecting to Map where key is foo.b.date and value is foo itself:
Map<String, Map<String, Foo>> map =
myList.stream()
.collect(Collectors.groupingBy(f -> f.b.id)) // map {Foo.b.id -> List<Foo>}
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), // id
e -> e.getValue().stream() // stream of foos
.collect(Collectors.toMap(f -> f.b.date,
f -> f))));
Or even more simple:
Map<String, Map<String, Foo>> map =
myList.stream()
.collect(Collectors.groupingBy(f -> f.b.id,
Collectors.toMap(f -> f.b.date,
f -> f)));
An alternative is to support the equality contract on your key, Bar:
class Bar {
String id;
String date;
public boolean equals(Object o){
if (o == null) return false;
if (!o.getClass().equals(getClass())) return false;
Bar other = (Bar)o;
return Objects.equals(o.id, id) && Objects.equals(o.date, date);
}
public int hashCode(){
return id.hashCode*31 + date.hashCode;
}
}
Now you can just have a Map<Bar, Foo>.
I have two maps m1 and m2 of type Map<Integer, String>, which has to be merged into a single map
Map<Integer, List<String>>, where values of same keys in both the maps are collected into a List and put into a new Map.
Solution based on what I explored:
Map<Integer, List<String>> collated =
Stream.concat(m1.entrySet().stream(), m2.entrySet().stream()).collect(
Collectors.toMap(Entry::getKey,
Entry::getValue, (a, b) -> {
List<String> merged = new ArrayList<String>(a);
merged.addAll(b);
return merged;
}));
But, this solution expects source List to be Map<Integer, List<String>> as the merge function in toMap expects operands and result to be of the same type.
I don't want to change the source collection. Please provide your inputs on achieving this using lambda expression.
That's a job for groupingBycollector:
Stream.of(m1,m2)
.flatMap(m->m.entrySet().stream())
.collect(groupingBy(
Map.Entry::getKey,
mapping(Map.Entry::getValue, toList())
));
I can't test it right now, but I think all you need it to change the mapping of the value from Entry::getValue to a List that contains that value :
Map<Integer, List<String>> collated =
Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey,
e -> {
List<String> v = new ArrayList<String>();
v.add(e.getValue());
return v;
},
(a, b) -> {
List<String> merged = new ArrayList<String>(a);
merged.addAll(b);
return merged;
}));
EDIT: The idea was correct. The syntax wasn't. Current syntax works, though a bit ugly. There must be a shorter way to write it.
You can also replace e -> {..} with e -> new ArrayList<String>(Arrays.asList(new String[]{e.getValue()})).
or with e -> Stream.of(e.getValue()).collect(Collectors.toList())
Or you can do it with groupingBy :
Map<Integer, List<String>> collated =
Stream.concat(m1.entrySet().stream(), m2.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())));
Misha's solution is the best if you want pure Java-8 solution. If you don't mind using third-party libraries, it would be a little shorter using my StreamEx.
Map<Integer, List<String>> map = StreamEx.of(m1, m2)
.flatMapToEntry(Function.identity())
.grouping();
Internally it's the same as in Misha's solution, just syntactic sugar.
This seems like a great opportunity to use Guava's Multimap.
ListMultimap<Integer, String> collated = ArrayListMultimap.create();
collated.putAll(Multimaps.forMap(m1));
collated.putAll(Multimaps.forMap(m2));
And if you really need a Map<Integer, List<String>>:
Map<Integer, List<String>> mapCollated = Multimaps.asMap(collated);
You can use the Collectors.toMap method with three parameters for clarity.
Map<Integer, String> m1 = Map.of(1, "A", 2, "B");
Map<Integer, String> m2 = Map.of(1, "C", 2, "D");
// two maps into one
Map<Integer, List<String>> m3 = Stream
.of(m1.entrySet(), m2.entrySet())
.flatMap(Collection::stream)
.collect(Collectors.toMap(
// key - Integer
e -> e.getKey(),
// value - List<String>
e -> List.of(e.getValue()),
// mergeFunction - two lists into one
(list1, list2) -> {
List<String> list = new ArrayList<>();
list.addAll(list1);
list.addAll(list2);
return list;
}));
// output
System.out.println(m3); // {1=[A, C], 2=[B, D]}