Split String into Map using Java Streams - java

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]}

Related

List<String> to Map<String,String> with stream, with String splitting

I have List with Strings:
List<String> cookiesUpdate = Arrays.asList("A=2" , "B=3");
I want to convert it to Map:
{
"A": "2",
"B": "3"
}
Code:
Map<String, String> cookies = cookiesUpdate.stream()
.collect(Collectors.toMap(String::toString, String::toString));
How to write those splitters above? If compiler thinks key String is Object.
.split("=")[0];
.split("=")[1];
Split should be done by "=" (or "\\s*=\\s" to exclude whitespaces around =)
Update Also it is better to provide limit argument to String::split to split at the first occurrence of "=", thanks #AndrewF for suggestion!
Fix toMap collector to use the first element of the array as key and the last as the value; a merge function may be needed if several namesake cookies are possible
Map<String, String> map = cookies.stream()
.map(ck -> ck.split("\\s*=\\s*", 2)) // Stream<String[]>
.filter(arr -> arr.length > 1) // ignore invalid cookies
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1], (v1, v2) -> v1));
If there are multiple cookies with the same name, it may be worth to collect them into Set<String> thus keeping all unique values. For this, Collectors.groupingBy and Collectors.mapping:
Map<String, Set<String>> map2 = cookies.stream()
.map(ck -> ck.split("\\s*=\\s*", 2)) // Stream<String[]>
.filter(arr -> arr.length > 1) // ignore invalid cookies
.collect(Collectors.groupingBy(
arr -> arr[0],
Collectors.mapping(arr -> arr[1], Collectors.toSet())
));

Java Stream - Combine Two Streams

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

Java 8 stream sort by maximum duplicates and then distinct

Using a stream, how to sort a list of objects by field (in my case ,componentCode) that has the maximum number of duplicates, and then find distinct
I tried something like this, but how to add the size of the duplicates when sorting.
List<String> conflictingComponentsCode = componentWarnings.stream()
.sorted(Comparator.comparing(ComponentErrorDetail::getComponentCode))
.map(ComponentErrorDetail::getComponentCode)
.distinct()
.collect(Collectors.toList());
Very similar to #nafas option:
List<String> conflictingComponentsCode = componentWarnings.stream()
.map(ComponentErrorDetail::getComponentCode)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
You can check this question for another example of grouping by count: Group by counting in Java 8 stream API
the idea is to use Collectors.groupingBy function to make a map (value to count), then sort the map in reverse order then map back to list again.
here a rough implementation:
List<String> conflictingComponentsCode =
componentWarnings.stream()
.map(ComponentErrorDetail::getComponentCode).collect(
Collectors.groupingBy(
Function.identity(), Collectors.counting())
)
//sort map
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue()
.reversed())
//map to list
.map(entry -> entry.key()).collect(Collectors.toList());
;

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]));

split string and store it into HashMap java 8

I want to split below string and store it into HashMap.
String responseString = "name~peter-add~mumbai-md~v-refNo~";
first I split the string using delimeter hyphen (-) and storing it into ArrayList as below:
public static List<String> getTokenizeString(String delimitedString, char separator) {
final Splitter splitter = Splitter.on(separator).trimResults();
final Iterable<String> tokens = splitter.split(delimitedString);
final List<String> tokenList = new ArrayList<String>();
for(String token: tokens){
tokenList.add(token);
}
return tokenList;
}
List<String> list = MyClass.getTokenizeString(responseString, "-");
and then using the below code to convert it to HashMap using stream.
HashMap<String, String> = list.stream()
.collect(Collectors.toMap(k ->k.split("~")[0], v -> v.split("~")[1]));
The stream collector doesnt work as there is no value against refNo.
It works correctly if I have even number of elements in ArrayList.
Is there any way to handle this? Also suggest how I can use stream to do these two tasks (I dont want to use getTokenizeString() method) using stream java 8.
Unless Splitter is doing any magic, the getTokenizeString method is obsolete here. You can perform the entire processing as a single operation:
Map<String,String> map = Pattern.compile("\\s*-\\s*")
.splitAsStream(responseString.trim())
.map(s -> s.split("~", 2))
.collect(Collectors.toMap(a -> a[0], a -> a.length>1? a[1]: ""));
By using the regular expression \s*-\s* as separator, you are considering white-space as part of the separator, hence implicitly trimming the entries. There’s only one initial trim operation before processing the entries, to ensure that there is no white-space before the first or after the last entry.
Then, simply split the entries in a map step before collecting into a Map.
First of all, you don't have to split the same String twice.
Second of all, check the length of the array to determine if a value is present for a given key.
HashMap<String, String> map=
list.stream()
.map(s -> s.split("~"))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
This is assuming you want to put the key with a null value if a key has no corresponding value.
Or you can skip the list variable :
HashMap<String, String> map1 =
MyClass.getTokenizeString(responseString, "-")
.stream()
.map(s -> s.split("~"))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
private final String dataSheet = "103343262,6478342944, 103426540,84528784843, 103278808,263716791426, 103426733,27736529279,
103426000,27718159078, 103218982,19855201547, 103427376,27717278645,
103243034,81667273413";
final int chunk = 2;
AtomicInteger counter = new AtomicInteger();
Map<String, String> pairs = Arrays.stream(dataSheet.split(","))
.map(String::trim)
.collect(Collectors.groupingBy(i -> counter.getAndIncrement() / chunk))
.values()
.stream()
.collect(toMap(k -> k.get(0), v -> v.get(1)));
result:
pairs =
"103218982" -> "19855201547"
"103278808" -> "263716791426"
"103243034" -> "81667273413"
"103426733" -> "27736529279"
"103426540" -> "84528784843"
"103427376" -> "27717278645"
"103426000" -> "27718159078"
"103343262" -> "6478342944"
We need to group each 2 elements into key, value pairs, so will partion the list into chunks of 2, (counter.getAndIncrement() / 2) will result same number each 2 hits ex:
IntStream.range(0,6).forEach((i)->System.out.println(counter.getAndIncrement()/2));
prints:
0
0
1
1
2
2
You may use the same idea to partition list into chunks.
Another short way to do :
String responseString = "name~peter-add~mumbai-md~v-refNo~";
Map<String, String> collect = Arrays.stream(responseString.split("-"))
.map(s -> s.split("~", 2))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
System.out.println(collect);
First you split the String on basis of - , then you map like map(s -> s.split("~", 2))it to create Stream<String[]> like [name, peter][add, mumbai][md, v][refNo, ] and at last you collect it to toMap as a[0] goes to key and a[1] goes to value.

Categories