Java Stream - Combine Two Streams - java

Is there a way I can combine these two streams into one?
Here's the first stream
Map<String, String> rawMapping = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("first_family"))
.findAny()
.map(columns -> columns.getColumns().stream()).get()
.collect(Collectors.toMap(
Column::getPrefix,
Column::getValue
));
Second stream
List<Token> tokenValues = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("second_family"))
.findAny()
.map(columns -> columns.getColumns().stream()).get()
.map(token -> {
return Token.builder()
.qualifier(token.getPrefix())
.raw(rawMapping.get(token.getPrefix()))
.token(token.getValue())
.build();
})
.collect(Collectors.toList());
Basically tokens is a list which has two column family, my goal is to create a list which will combine the value of the two-column family based on their qualifier. The first stream is storing the first column family into a map. The second stream is traversing the second family and getting the value thru the map using the qualifier and storing it into a new list.

you can use double filtering and then later you might use a flat map then to get a list:
Map<String, String> tokenvalues = tokens.getColumnFamilies().stream()
.filter(family -> family.getName().equals("first_family"))
.filter(family -> family.getName().equals("second_family"))
.map(columns -> columns.getColumns().stream())
//etc..
.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()));
you can remake a stream out of it inline
https://www.baeldung.com/java-difference-map-and-flatmap

Related

Split String into Map using Java Streams

I want to split the following String and store it into a Map.
String = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>"
The string can have line breaks in between the pairs. A key can have multiple values that are separated by a , and begin with a < and end with a >.
Now this String needs to be converted to a Map<String, List<String>>.
The structure of the map should look like this:
key_a={value_a1},
key_b={value_b1},
key_c={value_c1, value_c2, value_c3}
I currently only have the logic for splitting apart the different key-value-pairs from each other, but I don't know how to implement the logic that splits the values apart from each other, removes the brackets and maps the attributes.
String strBody = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>"
Map<String, List<String>> map = Pattern.compile("\\r?\\n")
.splitAsStream(strBody)
.map(s -> s.split(":"))
//...logic for splitting values apart from each other, removing <> brackets and storing it in the map
)
You can filter the arrays having two values and then use Collectors.groupingBy to group the elements into Map, You can find more examples here about groupingBy and `mapping
Map<String, List<String>> map = Pattern.compile("\\r?\\n")
.splitAsStream(strBody)
.map(s -> s.split(":"))
.filter(arr -> arr.length == 2)
.collect(Collectors.groupingBy(arr -> arr[0],
Collectors.mapping(arr -> arr[1].replaceAll("[<>]", ""),
Collectors.toList())));
An additional approach which also splits the list of values:
Map<String,List<String>> result =
Pattern.compile("[\\r\\n]+")
.splitAsStream(strBody)
.map(s -> s.split(":"))
.map(arr -> new AbstractMap.SimpleEntry<>(
arr[0],
Arrays.asList(arr[1].replaceAll("[<>]", "").split("\\s*,\\s"))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Your input has two \r\n to separate the entries, you need to split it by it as well, otherwise you will get empty entries, which you then need to filter out.
I'd remove the angle brackets from the string before processing it in the stream.
And then only the step of collection remains.
Map<String, String> map = Pattern.compile("\\r?\\n\\r?\\n")
.splitAsStream(strBody.replaceAll("[<>]",""))
.map(s -> s.split(":"))
.collect(Collectors.toMap(e -> e[0], e-> e[1]));
Try this.
String strBody = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>";
Map<String, List<String>> result = Arrays.stream(strBody.split("\\R\\R"))
.map(e -> e.split(":", 2))
.collect(Collectors.toMap(a -> a[0],
a -> List.of(a[1].replaceAll("^<|>$", "").split("\\s,\\s*"))));
System.out.println(result);
output
{key_c=[value_c1, value_c2, value_c3], key_b=[value_b1], key_a=[value_a1]}

Lambda & Stream : collect in a Map

I would like to build a Map using the Stream & Lambda couple.
I've tried many ways but I'm stucked. Here's the classic Java code to do it using both Stream/Lambda and classic loops.
Map<Entity, List<Funder>> initMap = new HashMap<>();
List<Entity> entities = pprsToBeApproved.stream()
.map(fr -> fr.getBuyerIdentification().getBuyer().getEntity())
.distinct()
.collect(Collectors.toList());
for(Entity entity : entities) {
List<Funder> funders = pprsToBeApproved.stream()
.filter(fr -> fr.getBuyerIdentification().getBuyer().getEntity().equals(entity))
.map(fr -> fr.getDocuments().get(0).getFunder())
.distinct()
.collect(Collectors.toList());
initMap.put(entity, funders);
}
As you can see, I only know how to collect in a list, but I just can't do the same with a map. That's why I have to stream my list again to build a second list to, finally, put all together in a map.
I've also tried the 'collect.groupingBy' statement as it should too produce a map, but I failed.
It seems you want to map whatever is on the pprsToBeApproved list to your Funder instances, grouping them by buyer Entity.
You can do it as follows:
Map<Entity, List<Funder>> initMap = pprsToBeApproved.stream()
.collect(Collectors.groupingBy(
fr -> fr.getBuyerIdentification().getBuyer().getEntity(), // group by this
Collectors.mapping(
fr -> fr.getDocuments().get(0).getFunder(), // mapping each element to this
Collectors.toList()))); // and putting them in a list
If you don't want duplicate funders for a particular entity, you can collect to a map of sets instead:
Map<Entity, Set<Funder>> initMap = pprsToBeApproved.stream()
.collect(Collectors.groupingBy(
fr -> fr.getBuyerIdentification().getBuyer().getEntity(),
Collectors.mapping(
fr -> fr.getDocuments().get(0).getFunder(),
Collectors.toSet())));
This uses Collectors.groupingBy along with Collectors.mapping.

Grouping By without using a POJO in java 8

I have a use case where I need to read a file and get the grouping of a sequence and a list of values associated with the sequence. The format of these records in the file are like sequence - val , example
10-A
10-B
11-C
11-A
I want the output to be a map (Map<String,List<String>>) with the sequence as the key and list of values associated with it as value, like below
10,[A,B]
11,[C,A]
Is there a way I can do this without creating a POJO for these records? I have been trying to explore the usage of Collectors.groupingBy and most of the examples I see are based on creating a POJO.
I have been trying to write something like this
Map<String, List<String>> seqCpcGroupMap = pendingCpcList.stream().map(rec ->{
String[] cpcRec = rec.split("-");
return new Tuple2<>(cpcRec[0],cpcRec[1])
}).collect(Collectors.groupingBy(x->x.))
or
Map<String, List<String>> seqCpcGroupMap = pendingCpcList.stream().map(rec ->{
String[] cpcRec = rec.split("-");
return Arrays.asList(cpcRec[0],cpcRec[1]);
}).collect(Collectors.groupingBy(x->(ArrayList<String>)x[0]));
I am unable to provide any key on which the groupingBy can happen for the groupingBy function, is there a way to do this or do I have to create a POJO to use groupingBy?
You may do it like so,
Map<String, List<String>> result = source.stream()
.map(s -> s.split("-"))
.collect(Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())));
Alternatively, you can use Map.computeIfAbsent directly as :
List<String> pendingCpcList = List.of("10-A","10-B","11-C","11-A");
Map<String, List<String>> seqCpcGroupMap = new HashMap<>();
pendingCpcList.stream().map(rec -> rec.split("-"))
.forEach(a -> seqCpcGroupMap.computeIfAbsent(a[0], k -> new ArrayList<>()).add(a[1]));

I try to find a simple way in Java 8 stream API to do the grouping

I try to find a simple way in Java 8 stream API to do the grouping.
Path caminho = Paths.get(System.getProperty("user.dir"), "log.txt");
Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> {
return getUrl(linha);
})
.filter(url -> url != null)
.collect(
Collectors.groupingBy(Arquivo::getUrl,
LinkedHashMap::new, Collectors.counting() ),
Collectors.groupingBy(Arquivo::getStatusCode,
LinkedHashMap::new, Collectors.counting()));
it is possible?
You didn’t describe your use case, so one possible answer is that you can just group both properties into a single map, assuming that these properties have no overlapping value, which would be always the case, if they have different type:
Path caminho = Paths.get(System.getProperty("user.dir"), "log.txt");
LinkedHashMap<Object, Long> map = Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> getUrl(linha))
.filter(Objects::nonNull)
.flatMap(arquivo -> Stream.of(arquivo.getUrl(), arquivo.getStatusCode()))
.collect(Collectors.groupingBy(Function.identity(),
LinkedHashMap::new, Collectors.counting() ));
for the resulting map, lookup of either key would succeed and also iterating over it would yield to the right order, you’ll only have to skip the keys of the type you’re not interested in.
If both properties have the same type, say e.g. String, and could have overlapping values, including a marker would help:
Map<Boolean, LinkedHashMap<String, Long>> map
= Files.lines(caminho, StandardCharsets.ISO_8859_1)
.map(linha -> getUrl(linha))
.filter(Objects::nonNull)
.flatMap(arquivo -> Stream.of(Arrays.asList(arquivo.getUrl(), true),
Arrays.asList(arquivo.getStatusCode(), false)))
.collect(Collectors.partitioningBy(l -> (Boolean)l.get(1),
Collectors.groupingBy(l -> (String)l.get(0),
LinkedHashMap::new, Collectors.counting() )));
LinkedHashMap<String, Long> urls = map.get(true);
LinkedHashMap<String, Long> status = map.get(false);
This does not only solve the overlapping, but also provides you with two distinct maps, but unfortunately, it doesn’t work so smoothly, if the two properties have different types.

Merge two List value maps

Anybody knows how to merge with Java 8 two maps of this type?
Map<String, List<String>> map1--->["a",{1,2,3}]
Map<String, List<String>> map2--->["a",{4,5,6}]
And obtain as result of the merge
Map<String, List<String>> map3--->["a",{1,2,3,4,5,6}]
I´m looking for a non verbose way if exist. I know how to do it in the old fashion way.
Regards.
The general idea is the same as in this post. You create a new map from the first map, iterate over the second map and merge each key with the first map thanks to merge(key, value, remappingFunction). In case of conflict, the remapping function is applied: in this case, it takes the two lists and merges them; if there is no conflict, the entry with the given key and value is put.
Map<String, List<String>> mx = new HashMap<>(map1);
map2.forEach((k, v) -> mx.merge(k, v, (l1, l2) -> {
List<String> l = new ArrayList<>(l1);
l.addAll(l2);
return l;
}));
You could try this, which gradually flattens the structure until you have a stream of tuples of the maps keys versus the lists values:
Map<K,List<V>> result = Stream.of(map1,map2) // Stream<Map<K,List<V>>>
.flatMap(m -> m.entrySet().stream()) // Stream<Map.Entry<K,List<V>>>
.flatMap(e -> e.getValue().stream() // Inner Stream<V>...
.map(v -> new AbstractMap.SimpleImmutableEntry<>(e.getKey(), v)))
// ...flatmapped into an outer Stream<Map.Entry<K,V>>>
.collect(Collectors.groupingBy(e -> e.getKey(), Collectors.mapping(e -> e.getValue(), Collectors.toList())));
Another option would avoid the internal streaming of the lists by using Collectors.reducing() as a second parameter of groupingBy, I guess. However, I would consider the accepted answer first
You have to use Set instead of List and can do it like this:
Map<String, Set<String>> map1--->["a",{1,2,3}]
Map<String, Set<String>> map2--->["a",{4,5,6}]
map1.forEach((k, v) -> v.addAll(map2.get(k) == null : new HashSet<> ? map2.get(k)));

Categories