Collect stream of EntrySet to LinkedHashMap - java

I want to collect the stream to a LinkedHashMap<String, Object>.
I have a JSON resource that is stored in LinkedHashMap<String, Object> resources.
Then I filter out JSON elements by streaming the EntrySet of this map.
Currently I am collecting the elements of stream to a regular HashMap. But after this I am adding other elements to the map. I want these elements to be in the inserted order.
final List<String> keys = Arrays.asList("status", "createdDate");
Map<String, Object> result = resources.entrySet()
.stream()
.filter(e -> keys.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
result.put("date", "someDate");
return result;
That is why I want to collect the stream to a LinkedHashMap<String, Object>. How can I achieve this?

You can do this with Stream:
Map<String, Object> result = resources.entrySet()
.stream()
.filter(e -> keys.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x, y) -> y, LinkedHashMap::new));
The part (x, y) -> y is because of mergeFunction when find duplicate keys, it returns value of second key which found. the forth part is mapFactory which a supplier providing a new empty Map into which the results will be inserted.

An alternate way of doing this using Map.forEach is:
Map<String, Object> result = new LinkedHashMap<>();
resources.forEach((key, value) -> {
if (keys.contains(key)) {
result.put(key, value);
}
});
result.put("date", "someDate");
and if you could consider iterating on the keySet as an option:
Map<String, Object> result = new LinkedHashMap<>();
resources.keySet().stream()
.filter(keys::contains)
.forEach(key -> result.put(key,resources.get(key)));
result.put("date", "someDate");

Related

Collecting a basic map in Java 8 streams API

Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.filter(THING_1)
.filter(THING_2)
.collect(Collectors.toMap(
Map.Entry<String, String>::getKey, Map.Entry<String, String>::getValue));
getUrls - returns Map<String, String>
how can I collect this to a Map<String, String>? The first map returns a Map<String, String> but I can't get the compiler to stop complaining.
If getUrls() returns a Map, you can't collect it as an Entry. If the idea is to combine all the maps, you can use flatMap() to merge their entries into one stream:
Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.map(Map::entrySet)
.flatMap(Set::stream)
.filter(THING_1)
.filter(THING_2)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Alternatively, you can use a custom collector to fold each result into a single map:
Map<String, String> preFilteredKafkaRecords = kafkaRecordMap.entrySet().stream()
.map(item -> getUrls(item.getKey(), item.getValue()))
.filter(THING_1)
.filter(THING_2)
.collect(HashMap::new, Map::putAll, Map::putAll);

HashMap transformation using streams

I have Map<Long, Map<String, String>> map, and I have to filter that by key and further get only value.
I'm trying to do some like that:
Map<Object, Object> resultMap = map.entrySet().stream()
.filter(x -> x.getKey().equals(filterValue))
.map(Map.Entry::getValue).collect(Collectors.toMap(k -> k,v -> v));
But I got Map<Object, Object> map instead of Map<String, String>.
Maybe, there is some better way to do it.
The following should work as you need:
Map<String, String> resultMap = map.entrySet().stream()
.filter(x -> x.getKey().equals(filterValue))
.flatMap(entry -> entry.getValue().entrySet().stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
You should set values of map to specific types in collector
.collect(Collectors.toMap(Object::toString, Object::toString))
From map.entrySet().stream().filter(x -> x.getKey().equals(filterValue)), it could be understood that filterValue is a long. There is no need to stream a map to filter out the values matching a particular key. Because map keys are unique, and there will be only one matching key. You could have just used:
Map<String, String> result = map.get(filterValue);

Java 8 stream grouping a List<Map<>> by the same <Key, Value> to a new List<Map<>>

I have a List<Map<String,String>>
such as:
Map<String, String> m1 = new HashMap<>();
m1.put("date", "2020.1.5");
m1.put("B", "10");
Map<String, String> m2 = new HashMap<>();
m2.put("date", "2020.1.5");
m2.put("A", "20");
Map<String, String> m3 = new HashMap<>();
m3.put("date", "2020.1.6");
m3.put("A", "30");
Map<String, String> m4 = new HashMap<>();
m4.put("date", "2020.1.7");
m4.put("C", "30");
List<Map<String, String>> before = new ArrayList<>();
before.add(m1);
before.add(m2);
before.add(m3);
before.add(m4);
My expect result is to generate a new List map, which is grouped by date , and all the entry set in the same date would be put together, like:
[{"A":"20","B":"10","date":"2020.1.5"},{"A":"30","date":"2020.1.6"},{"C":"30","date":"2020.1.7"}]
I tried with the following method, but always not my expect result.
stream().flatmap().collect(Collectors.groupingBy())
Some Additional Comments for this problem:
I worked this out with for LOOP, but the application hangs when the list size is about 50000, so I seek a better performant way to do this. Java 8 stream flat map is a perhaps way as far as I know.
So the key point is not only to remap this but also with the most performant way to do this.
before
.stream()
.collect(Collectors.toMap((m) -> m.get("date"), m -> m, (a,b) -> {
Map<String, String> res = new HashMap<>();
res.putAll(a);
res.putAll(b);
return res;
}))
.values();
This is the solution you're looking for.
The toMap function receives 3 parameters:
the key mapper, which in your case is the date
the value mapper, which is the map itself that's being processed
the merge function, which takes 2 maps with the same date and puts all the keys together
Output:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
You can do this way using groupingBy and Collector.of
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collector.of( HashMap<String,String>::new,
(m,e)-> m.putAll(e),
(map1,map2)->{ map1.putAll(map2); return map1;}
))).values());
Here, first use Collectors.groupingBy to group by date. Then define custom collector using Collector.of to collect List<Map<String, String>> into Map<String, String>. After create list using map values.
And using Collectors.flatMapping from Java 9
List<Map<String, String>> list = new ArrayList<>(before.stream()
.collect(Collectors.groupingBy(
k -> k.get("date"),
Collectors.flatMapping(m -> m.entrySet().stream(),
Collectors.toMap(k -> k.getKey(), v -> v.getValue(), (a,b) -> a))))
.values());
You can achieve the very same result using a certain number of Collectors, orderly:
Collectors.groupingBy to group by the date
Collectors.reducing to merge the Map<String, String> items
Collectors.collectingAndThen to transform the values from Map<String, Optional<Map<String, String>>>, as a result of the previous reducing to the final output List<Map<String, String>>.
List<Map<String, String>> list = before.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
m -> m.get("date"),
Collectors.reducing((l, r) -> {
l.putAll(r);
return l; })
),
o -> o.values().stream()
.flatMap(Optional::stream)
.collect(Collectors.toList())));
The list contains what are you looking for:
[{date=2020.1.5, A=20, B=10}, {date=2020.1.6, A=30}, {date=2020.1.7, C=30}]
Important: This solution has two he disadvantages:
It looks clumsy and might not be clear for an independent viewer
It mutates (modifies) the original maps included in the List<Map<String, String>> before.
It can be done as follows:
List<Map<String, String>> remapped = before.stream()
.collect(Collectors.groupingBy(m -> m.get("date")))
.values().stream()
.map(e -> e.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x1, x2) -> x1)))
.collect(Collectors.toList());
remapped.forEach(System.out::println);
Output:
{date=2020.1.5, A=20, B=10}
{date=2020.1.6, A=30}
{date=2020.1.7, C=30}

Convert Java Map Enum keys to Map String keys

I have the following map:
Map<DataFields, String> myMap;
But I need to convert it to the following:
Map<String, String> myMap;
My best feeble attempt which doesn't even compile is:
myMap.keySet().stream().map(k -> k.name()).collect(Collectors.toMap(k, v)
You need to stream the entrySet() (so you have both the keys and the values), and collect them to a map:
Map<String, String> result =
myMap.entrySet()
.stream()
.collect(Collectors.toMap(e -> e.getKey().name(), e -> e.getValue()));
Map<String, String> result = myMap
.entrySet() // iterate over all entries (object with tow fields: key and value)
.stream() // create a stream
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue()));
// collect to map: convert enum Key value toString() and copy entry value
Another way of doing same without Collectors helper. Using entryset will make it very easy to map.
map.entrySet()
.stream()
.collect(
() -> new HashMap<String, String>(),
(Map newMap, Map.Entry<DataFields, String> entry) -> {
newMap.put(entry.getKey().name(), entry.getValue());
}
,
(Map map1, Map map2) -> {
map.putAll(map2);
}
);
A Java 8, succint way to do it (without streams):
Map<String, String> result = new HashMap<>();
myMap.forEach((k, v) -> result.put(k.name(), v));

Sort map by key string (where key is actually an integer)

I am trying to sort a map to show in a dropdown. But I am not able to get any sorting done. This will return a new map. But not with the map sorted by the key as I would expect.
private Map<String, String> mapInstrumentIDs = new TreeMap<>();
Map<Object, Object> val = mapInstrumentIDs
.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
I of course didn't think about that the key is actually an integer. This means sorting it as a string does not give me the expected result (as integer sort). Changing the key to Integer and converting the value will yield the expected result.
By default a TreeMap guarantees that its elements will be sorted in ascending key order.
You should collect the results into a Map implementation that retains the order of its entries. LinkedHashMap will do:
Map<String, String> sorted = mapInstrumentIDs.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(x,y)-> {throw new AssertionError();},
LinkedHashMap::new
));
Normally one would make a copy as:
private SortedMap<String, String> mapInstrumentIDs = new TreeMap<>();
SortedMap<String, String> val = new TreeMap(mapInstrumentIDs);
If you want a copy with key type Comparable<?> and value type Object, you want to specify the initial map as TreeMap and cannot use the standard collectors:
SortedMap<Comparable, Object> val = mapInstrumentIDs
.entrySet()
.collect(TreeMap<Comparable, Object>::new,
(m, e) -> { m.put(e.getKey(), e.getValue()); return m; },
(m, m2) -> m.addAll(m2)
);

Categories