List of Strings to hashmap using Streams - java

I have a "," separated String array like this
a b c d,
f b h j,
l p o i,
I would like this to be converted to a Hashmap like
HashMap<String, List<String>> such that second element in list (delimited by space becomes key and the 3rd element becomes value)
So,
This should become
b -> c,h
p -> o
I want to use Streams API and I think this is the way to go:
List<String> entries = new ArrayList<>();
HashMap<String, List<String>> map = new HashMap<>();
HashMap<String, List<String>> newMap = entries.stream()
.collect(line -> {
if (map.contains(line.split(" ")[1])) {
// Get existing list and add the element
map.get(line.split(" ")[1].add(line.split(" ")[1]));
} else {
// Create a new list and add
List<String> values = new ArrayList<>();
values.add(line.split(" ")[1]);
map.put(line.split(" ")[0], values);
}
});
Is there any better way? How exactly should I return Hashmap from collect function?

You can use the Collectors.groupingBy as shown below to group the inputs (follow the inline comments):
String[] inputs = {"a b c d,", "f b h j,", "l p o i,"};
Map<String, List<String>> results =
Arrays.stream(inputs).map(s -> s.split(" ")).//splt with space
collect(Collectors.groupingBy(arr -> arr[1], // Make second element as the key
Collectors.mapping(arr -> arr[2], // Make third element as the value
Collectors.toList())));//collect the values to List
System.out.println(results);
Output:
{p=[o], b=[c, h]}
I suggest you read the API here to understand how Collectors.groupingBy along with Collectors.mappingworks.

You can achieve the task at hand using a groupingBy collector along with Collectors.mapping as a downstream collector.
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(a -> a[1],
Collectors.mapping(a -> a[2], Collectors.toList())));
output:
{p=[o], b=[c, h]}
if you want to maintain insertion order then you can specify a LinkedHashMap like this:
Map<String, List<String>> collect =
myList.stream()
.map(s -> s.split(" "))
.collect(Collectors.groupingBy(s -> s[1],
LinkedHashMap::new,
Collectors.mapping(s -> s[2], Collectors.toList())));
output:
{b=[c, h], p=[o]}

If you want HashMap , not just any Map
HashMap<String, List<String>> output =myList.stream().map(s -> s.split(" "))
.collect(Collectors.groupingBy((s) -> s[1],
HashMap::new,
Collectors.mapping(
(s) -> s[2],
Collectors.toList())));

Related

How to split a string into a map, grouping values by duplicate keys using streams?

I want to convert the following
String flString="view1:filedname11,view1:filedname12,view2:fieldname21";
to a Map<String,Set<String>> to get the key/value as below:
view1=[filedname11,filedname12]
view2=[fieldname21]
I want to use Java 8 streams. I tried
Arrays.stream(tokens)
.map(a -> a.split(":"))
.collect(Collectors.groupingBy(
a -> a[0], Collectors.toList()));
However the keys are also getting added to the value list.
You should use a Collectors::mapping to map the array to an element.
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, List<String>> map = Pattern.compile(",")
.splitAsStream(flString)
.map(a -> a.split(":"))
.collect(
Collectors.groupingBy(a -> a[0],
Collectors.mapping(a -> a[1], Collectors.toList())
)
);
map.entrySet().forEach(System.out::println);
Output
view1=[filedname11, filedname12]
view2=[fieldname21]
You can use Collectors#toMap(keyMapper,valueMapper,mergeFunction) method as follows:
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, Set<String>> map = Arrays
.stream(flString.split(","))
.map(str -> str.split(":"))
.collect(Collectors.toMap(
arr -> arr[0],
arr -> new HashSet<>(Set.of(arr[1])),
(s1, s2) -> {
s1.addAll(s2);
return s1;
}));
System.out.println(map);
// {view1=[filedname11, filedname12], view2=[fieldname21]}
Off course #K.Nicholas solution with using downstream collector is perfect.
But I additionally created another alternative solution that also uses Stream API.
It is more complex and generates three streams, but it works too.
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, Set<String>> map1 =
Arrays.stream(flString.split(","))
.map(a -> a.split(":"))
.collect(Collectors.toMap(
a -> a[0],
a -> a[1],
(a1, a2) -> a1 + "," + a2))
.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey(),
e -> Arrays.stream(e.getValue().split(","))
.collect(Collectors.toSet())));
map1.entrySet().forEach(System.out::println);
You can achieve your goal using the following piece of codes:
String flString = "view1:filedname11,view1:filedname12,view2:fieldname21";
Map<String, List<String>> collect = Stream.of(flString)
.flatMap(str -> Stream.of(str.split(",")))
.map(keyValuePair -> keyValuePair.split(":"))
.collect(Collectors.groupingBy(it -> it[0], Collectors.mapping(it -> it[1], Collectors.toList())));
Simple output:
{view1=[filedname11, filedname12], view2=[fieldname21]}

How create Map<String,List<Long>> java 8 with single stream?

I have to create a Map<String,List<Long>> (possibly with single stream ) with key= name of the course , value= number of times the course was chosen as first choice (first entry of the list) ,second choice (second entry of the list) ,third choice (third entry of the list) ,
for Example : Chemstry, List< 4,6,7>
I tried with this but gives me errors:
return courses.values().stream()
.collect(groupingBy(Course::getNome,TreeMap::new, collectingAndThen(Course::getchoice, counting()));
The grouping and counting is fairly simple, but getting into a list takes a bit more work. Here's one way to do that by collectingAndThen streaming the counts:
courses.values()
.stream()
.collect(groupingBy(
Course::getName,
collectingAndThen(
groupingBy(Course::getChoice, counting()),
counts -> IntStream.range(0, 3)
.mapToObj(i -> counts.getOrDefault(i + 1, 0L))
.collect(toList()))))
Ideone Demo
EDIT: #Eugene suggests I've misunderstood the requirements. If you want to list all the choices rather than the top three, just replace 3 with Collections.max(counts.keySet()).
Just for the fun of it, if you are willing to do it in two steps:
static Map<String, List<Long>> group(Map<?, Course> courses) {
Map<String, List<Long>> m = courses.values()
.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
Course::getName,
Course::getChoice,
Math::max),
map -> map.entrySet().stream()
.collect(Collectors.toMap(
Entry::getKey,
e -> new ArrayList<>(Collections.nCopies(e.getValue(), 0L))))
));
courses.values()
.forEach(x -> {
List<Long> l = m.get(x.getName());
l.set(x.getChoice() - 1, l.get(x.getChoice() - 1) + 1);
});
return m;
}

Extract values (not keys) from a List<Map<String,String>>, and flatten it to a List<String>

How would I extract the values (not keys) from a List<Map<String,String>>, and flatten it to a List<String>?
i.e. tried the following but doesn't work.
List<Map<String,String>> mapList = .... ;
List<String> valueList = mapList.stream()
.map(o -> o.getValue())
.collect(Collectors.toList());
I'd like to filter the result by a given key as well.
You mean :
List<String> valueList = mapList.stream()
.flatMap(a -> a.values().stream())
.collect(Collectors.toList());
Edit
What if I want to specify a key e.g. I have "id" and "firstName", but
only want "firstName"
In this case you can use filter after the flatmap like so :
List<String> valueList = mapList.stream()
.flatMap(a -> a.entrySet().stream())
.filter (e -> e.getKey().equals("firstName"))
.map(Map.Entry::getValue)
.collect(Collectors.toList ());
Use .flatMap:
List<Map<String,String>> mapList = new ArrayList<>();
Map<String, String> mapOne = new HashMap<>();
mapOne.put("1", "one");
mapOne.put("2", "two");
Map<String, String> mapTwo = new HashMap<>();
mapTwo.put("3", "three");
mapTwo.put("4", "four");
mapList.add(mapOne);
mapList.add(mapTwo);
List<String> allValues = mapList.stream()
.flatMap(m -> m.values().stream())
.collect(Collectors.toList()); // [one, two, three, four]
Try
List<String> valueList = mapList.stream()
.flatMap(map -> map.entrySet().stream())
.filter(entry -> entry.getKey().equals("KEY"))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
The object o you are trying to map to o.getValue() is of type Map (which does not have a function getValue()), not Map.Entry (which would have such a function). What you can is get a Collection of the values with the function o.values().
You can then get a Stream from that collection, and flatten the resulting Stream of Streams like this:
List<String> valueList = mapList.stream()
.map(o -> o.values().stream())
.flatMap(Function.identity())
.collect(Collectors.toList());

Split list of string to list of map

I have the following list
[12_223,13_4356,15_5676]
I was able to spilt on underscore and convert this into one Hashmap using the below code
list.stream()
.map(s -> s.split("_"))
.collect(Collectors.toMap(
a -> a[0],
a -> a[1]));
it gives the below map
{"12"="223", "13"="4356", "15"="5676"}
But I wanted to change this code so that it gives me a list of maps like below as I might encounter duplicate keys while splitting
[{"12"="223"}, {"13"="4356"}, {"15"="5676"}]
If you really want the result to be a list of maps, I'd do it this way:
List<Map<String, String>> listOfMaps(List<String> list) {
return list.stream()
.map(s -> s.split("_", 2))
.map(a -> Collections.singletonMap(a[0], a[1]))
.collect(toList());
}
(HT: Eugene for recommending split("_", 2).)
In Java 9, you could do it this way:
List<Map<String, String>> listOfMaps(List<String> list) {
return list.stream()
.map(s -> s.split("_", 2))
.map(a -> Map.of(a[0], a[1]))
.collect(toList());
}
In both cases, the map instances in the resulting list will be unmodifiable.
This does what you are looking for
List<Map<String, String>> output = list.stream().map(
s-> {
Map<String, String> element = new HashMap<>();
String[] arr = s.split("_");
element.put(arr[0], arr[1]);
return element;
}).collect(Collectors.toList());
But as #Pshemo suggests, this should be more appropriate
Map<String, List<String>> outputnew = list.stream().map(
s -> s.split("_"))
.collect(Collectors.groupingBy(s -> s[0],
Collectors.mapping(s -> s[1], Collectors.toList())));

Swapping key from a Map<Key, List<Values>>

I'm searching a solution for this problem(it is for an exam):
I have a Map < String, SortedSet < String > > operators populated by a function
public void addOperator(String operatorName, String... destinationNames) throws ProposalException {
if(operators.containsKey((operatorName))){
throw new ProposalException("Operator " + operatorName + "already into system!");
}
else{
SortedSet<String> destinationstemp=new TreeSet<>();
for(String s: destinationNames){
if(s!=null){
destinationstemp.add(s);
}
}
operators.put(operatorName, destinationstemp);
}
Now, i want to create a new Map < String, SortedSet < String > > destinations that has as key the destinationName and as values the operatorNames related.
How can i make this out?
P.S: this one up there is the usage of the methods and the not-in-code part is the output wanted. Sorry for the bad formattation of the code. ph is the instance of the façade pattern class
public SortedSet<String> getDestOperators(String destinationName) {...}//method that returns the **destinations** values related to destinationName}
ph.addOperator("op3","london","rome");
ph.addOperator("op2","london","berlin");
ph.addOperator("op5","berlin","rome","madrid");
ph.addOperator("op1","london","madrid","berlin");
ph.addOperator("op10","rome");
ph.addOperator("op4","madrid","berlin");
System.out.println(ph.getDestOperators("madrid"));
Output: [op1, op4, op5]
you need to go through each entry in your map and check if inner set contains the value you are checking against,
public SortedSet<String> getDestOperators(String destinationName) {
Set<String> result = new HashSet<String>();
for(Map.Entry<String,Set<String>> entry : operators.getValues()){
if(entry.getValue().contains(destinationName)){
results.add(entry.getKey());
}
}
return result;
}
To get your example output a simple one-liner with streams:
List<String> result = operators.entrySet().stream().filter(entry -> entry.getValue().contains(destinationName)).map(Entry::getKey).sorted().collect(Collectors.toList());
or here for better readability spread over multiple lines:
List<String> result = operators
.entrySet()
.stream()
.filter(entry -> entry.getValue().contains(destinationName))
.map(Entry::getKey)
.sorted()
.collect(Collectors.toList());
A more complex one-liner if you want to "reverse" the mapping as described in your text:
Map<String, List<String>> result = operators.entrySet().stream().flatMap(entry -> entry.getValue().stream().collect(Collectors.toMap(Function.identity(), o -> Arrays.asList(entry.getKey()))).entrySet().stream()).collect(Collectors.toMap(Entry::getKey, Entry::getValue, (a, b) -> Stream.of(a, b).flatMap(List::stream).sorted().collect(Collectors.toList())));
or here for better readability spread over multiple lines:
Map<String, List<String>> result2 = operators
.entrySet()
.stream()
.flatMap(entry -> entry
.getValue()
.stream()
.collect(Collectors.toMap(Function.identity(),
o -> Arrays.asList(entry.getKey())))
.entrySet()
.stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue,
(a, b) -> Stream.of(a, b)
.flatMap(List::stream)
.sorted()
.collect(Collectors.toList())));
What you need to do, is loop over each operator, and then loop over all entries in the list, if value from the list is not yet present in your output map, you add it, else you modify its colection of operators.
Here is some code for you:
origin.forEach((key, list) -> {list.forEach(city -> {
if(result.containsKey(city))
result.get(city).add(key);
else{
SortedSet<String> set = new TreeSet<>();
set.add(key);
result.put(city, set);
});
});

Categories

Resources