Java 8 | Parallel Stream for a HashMap - java

In java 8 I know that they added the parallel stream which takes advantage of multicore processors, and I know that you can use it with something like this:
List<String> list = new ArrayList<String>();
list.parallelStream().forEach(str -> System.out.println(str));
But how would I achieve something like this with a HashMap?
Map<String, Integer> map = new HashMap<String, Integer>();
// won't work, because the Map class doesn't have the .parallelStream()
map.parallelStream().forEach((str, num) -> System.out.println(str + ":" + num));
Does anyone know how to do something like this? Thanks

You can't stream a Map directly, but you can stream its entry set, given with the entrySet() method. Extract the key and value from the entry object.
map.entrySet()
.parallelStream()
.forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue()));

You can get the 'entry set' from the hash map by calling map.entrySet(), you can call parallelStream() on the returned entry set.
Please note that the returned object is a set of Map.Entry. You can get the key and value from an entry set item by calling getKey() and getValue() on it respectively. As follows:
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
map.entrySet().parallelStream().forEach((e) -> System.out.println(e.getKey() + ":" + e.getValue()));

Related

Convert java list to map using stream with indexes

I'm trying to learn how to use the Java 8 collections and I was wondering if there was a way to convert my list to a map using a java stream.
List<PrimaryCareDTO> batchList = new ArrayList<>();
PrimaryCareDTO obj = new PrimaryCareDTO();
obj.setProviderId("123");
obj.setLocatorCode("abc");
batchList.add(obj);
obj = new PrimaryCareDTO();
obj.setProviderId("456");
obj.setLocatorCode("def");
batchList.add(obj);
I'm wondering how I would go about creating my list above into a map using a stream. I know how to use the foreach etc with puts, but I was just wondering if there was a more elegant way to build the map using a stream. (I'm aware the syntax below is not correct, I'm new to streams and not sure how to write it)
AtomicInteger index = new AtomicInteger(0);
Map<String, Object> result = batchList.stream()
.map("providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
.map("locatorCode" + index.get(), PrimaryCareDTO::getLocatorCode);
The goal is to represent the following.
Map<String, Object> map = new HashMap<>();
//Group a
map.put("providerId1", "123");
map.put("locatorCode1", "abc");
//Group b
map.put("providerId2", "456");
map.put("locatorCode2", "def");
...
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
...
AtomicInteger index = new AtomicInteger(0);
List<SimpleEntry<String, String>> providerIds =
batchList.stream()
.map(e -> new SimpleEntry<>("providerId" + index.incrementAndGet(), e.getProviderId()))
.collect(Collectors.toList());
index.set(0);
List<SimpleEntry<String, String>> locatorCodes =
batchList.stream()
.map(e -> new SimpleEntry<>("locatorCode" + index.incrementAndGet(), e.getLocatorCode()))
.collect(Collectors.toList());
Map<String, String> map = Stream.of(providerIds,
locatorCodes)
.flatMap(e -> e.stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
First it creates two lists, using Entry (from Map) to represent String-String tuples:
list with tuples providerId# as 'key' with the values e.g. "123"
list with tuples locatorCode# as 'key' with the values e.g. "abc"
It then creates a stream containing these two lists as 'elements', which are then concatenated with flatMap() to get a single long stream of Entry,
(The reason the first two can't stay stream and I have to go through a List and back to stream is because the two invocations of index.incrementAndGet() would otherwise only be evaluated when the streams are consumed, which is after index.set(0);.)
It then creates new key-value pairs with the counter and puts them into a map (with Collectors.toMap().
You would have to steam twice as you want to add two of the properties to map
AtomicInteger index = new AtomicInteger(1);
Map<String, String> result1 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
);
index.set(1);
Map<String, String> result2 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "locatorCode" + index.getAndIncrement(), PrimaryCareDTO::getLocatorCode)
);
Map<String, String> result = new HashMap<>();
result.putAll(result1);
result.putAll(result2);

Putting Map content into another Map, based on condition

I've tried searching for ways of doing this and they don't seem to solve my problem.
I have a Map:
Map<String,Element> elements = new HashMap<>();
I would like to put it into another Map; given a simple condition:
if (key.charAt(0) == 'W') //Does the start of the key have a "W"
How can I do this?
You can utilize Java 8 to perform the following:
elements.keySet().removeIf(key -> !key.startsWith("W"));
It removes every entry whose key does not start with W.
If you want to retain the first Map, simply create another Map first:
Map<String, Element> newElements = new HashMap<>(elements);
newElements.keySet().removeIf(key -> !key.startsWith("W"));
This worked just fine for me:
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String args[])
{
Map<String,String> elements = new HashMap<>();
Map<String,String> elements2 = new HashMap<>();//the second map
elements.put("A", new String("A"));
elements.put("B", new String("B"));
elements.put("W1", new String("W1"));
elements.put("W2", new String("W2"));
for (Map.Entry<String, String> entry : elements.entrySet())
{
if(entry.getKey().charAt(0) == 'W')
elements2.put(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : elements2.entrySet())
{
//printing the elements of the second map
System.out.println(entry.getKey() + "/" + entry.getValue());
}
}
}
giving the following output:
W1/W1
W2/W2
It depends on what your use case is. I'm going to assume you're using java 8.
If you want a new map with the filtered entries you could do
Map<String,Element> newElements = elements.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("W"))
.collect(Collectors.toMap(Entry::getKey(), Entry::getValue()));
If you already have the other map and you just want to add the entries to it you could use:
//assuming Map<String,Element> newElements = some appropriately initialized map
elements.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("W"))
.forEach(entry -> newElements.put(entry.getKey(), entry.getValue());
You could also pass the result of the first option to putAll on the second map if you'd rather. Though in that case I'd be partial to the second option.

Converting Map<K, V> to Map<V,List<K>>

I have map as below
Map<String, String> values = new HashMap<String, String>();
values.put("aa", "20");
values.put("bb", "30");
values.put("cc", "20");
values.put("dd", "45");
values.put("ee", "35");
values.put("ff", "35");
values.put("gg", "20");
I want to create new map in the format Map<String,List<String>> , sample output will be as
"20" -> ["aa","cc","gg"]
"30" -> ["bb"]
"35" -> ["ee","ff"]
"45" -> ["dd"]
I am able to do by iterating through entity
Map<String, List<String>> output = new HashMap<String,List<String>>();
for(Map.Entry<String, String> entry : values.entrySet()) {
if(output.containsKey(entry.getValue())){
output.get(entry.getValue()).add(entry.getKey());
}else{
List<String> list = new ArrayList<String>();
list.add(entry.getKey());
output.put(entry.getValue(),list);
}
}
Can this be done better using streams?
groupingBy can be used to group the keys by the values. If used without a mapping Collector, it will transform a Stream of map entries (Stream<Map.Entry<String,String>>) to a Map<String,List<Map.Entry<String,String>>, which is close to what you want, but not quite.
In order for the value of the output Map to be a List of the original keys, you have to chain a mapping Collector to the groupingBy Collector.
Map<String,List<String>> output =
values.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,
Collectors.toList())));
System.out.println (output);
Output :
{45=[dd], 35=[ee, ff], 30=[bb], 20=[aa, cc, gg]}
Note that in Java 8, you can also do better without using streams using Map.forEach and Map.computeIfAbsent. This way, it is more concise than the old version with Map.Entry<String, String>, entry.getValue(), entry.getKey() etc.
So you don't have to compare the old Java-7 iteration to that Java-8 stream solution, but to this one.
values.forEach( (key,value)->
groupBy.computeIfAbsent(value, x->new ArrayList<>())
.add(key)
);

Using lambda to format Map into String

I have a map with Integer keys and values. I need to transform it into a String with this specific format: key1 - val1, key2 - val2, key3 - val3. Now, I'm using forEach to format each element, collect them into a List, and then do String.join();
List<String> ships = new ArrayList<>(4);
for (Map.Entry<Integer, Integer> entry : damagedMap.entrySet())
{
ships.add(entry.getKey() + " - " + entry.getValue());
}
result = String.join(",", ships);
Is there any shorter way to do it? And it would be good to do it with lambda, because I need some practice using lambdas.
I think you're looking for something like this:
import java.util.*;
import java.util.stream.*;
public class Test {
public static void main(String[] args) throws Exception {
Map<Integer, String> map = new HashMap<>();
map.put(1, "foo");
map.put(2, "bar");
map.put(3, "baz");
String result = map.entrySet()
.stream()
.map(entry -> entry.getKey() + " - " + entry.getValue())
.collect(Collectors.joining(", "));
System.out.println(result);
}
}
To go through the bits in turn:
entrySet() gets an iterable sequence of entries
stream() creates a stream for that iterable
map() converts that stream of entries into a stream of strings of the form "key - value"
collect(Collectors.joining(", ")) joins all the entries in the stream into a single string, using ", " as the separator. Collectors.joining is a method which returns a Collector which can work on an input sequence of strings, giving a result of a single string.
Note that the order is not guaranteed here, because HashMap isn't ordered. You might want to use TreeMap to get the values in key order.

Sorting a hash map first according to value, then key secondarily

I need to sort a hash map, first according to value then according to key.
TreeMap<String,Integer> dictionary = new TreeMap<String,Integer>();
For example, the desired output is something like:
it - 2
of - 2
the - 2
times - 2
was - 2
best - 1
worst - 1
I tried tree map, but that only sorts it according to key.
So basically, I need to sort my first according to the Integer (Highest to Lowest) and then according to the String (First A then B till Z).
I searched online and saw that I'm gonna have to use comparator but I'm not sure how to make it work?
Any guidance will be appreciated.
This is for a Java assignment.
The Map interface has no concept of "order", but you could sort the entries:
Map<String, Integer> map; // populated
List<Map.Entry<String, Integer>> entries = new ArrayList<> (map.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<String, Integer>>() {
public int compareTo(Map.Entry<String, Integer> a, Map.Entry<String, Integer> b) {
return a.getValue().equals(b.getValue()) ? a.getKey().compareTo(b.getKey()) : Integer.compareTo(b.getValue(), a.getValue());
}
});
for (Map.Entry<String, Integer> entry : entries)
System.out.println(entry.getKey() + " - " + entry.getValue());
If java 8 is available, it becomes a lot neater:
Map<String, Integer> map; // populated
map.entrySet().stream()
.sorted( (a, b) -> a.getValue().equals(b.getValue()) ? a.getKey().compareTo(b.getKey()) : Integer.compareTo(b.getValue(), a.getValue()))
.map(e -> entry.getKey() + " - " + entry.getValue())
.forEach(System.out::println);
If absolutely need a map, load them into aLinkedHashMap, which "preserves order" in that it iterates over its entries in the same order they were inserted.
(I'm assuming this is something like Java). Sorted maps only deal with keys, not values. You probably want to just load the map content into a new TreeMap where each key is the original key and value combined somehow. Or, you can load into an List and sort that, or use a Set.

Categories

Resources