I have two Maps map1 and map2. I want to combine both these Maps in specific order.
Assume I have two maps
Map<String, String> map1 = new HashMap<>();
Map<String, String> map2 = new HashMap<>();
map1.put("id1", "3895");
map1.put("id2", "6754");
map1.put("id3", "7896");
map1.put("id4", "1122");
map2.put("month1", "Jan");
map2.put("month2", "Mar");
map2.put("month3", "Dec");
map2.put("month4", "Aug");
Now I want to combine these two maps so that the third map will have elements in below order.
Expected order in Map3.
("id1", "3895")
("month1", "Jan")
("id2", "6754")
("month2", "Mar")
("id3", "7896")
("month3", "Dec")
("id4", "1122")
("month4", "Aug")
How do I achieve this? I tried with putAll and LinkedHashMap but the resulting order is not the expected one.
With LinkedHashMap -
Map<String, String> merged = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (x, y) -> y, LinkedHashMap::new));
and the result is
("id1", "3895")
("id2", "6754")
("id3", "7896")
("id4", "1122")
("month1", "Jan")
("month2", "Mar")
("month3", "Dec")
("month4", "Aug")
which is not my expected one.
As suggested by #Andreas, you can iterate over maps in parallel and to LinkedHashMap to maintain order,
Map<String, String> result = new LinkedHashMap<>();
Iterator<Map.Entry<String, String>> iter1 = map1.entrySet().iterator();
Iterator<Map.Entry<String, String>> iter2 = map2.entrySet().iterator();
while(iter1.hasNext() || iter2.hasNext()) {
Map.Entry<String, String> e1 = iter1.next();
Map.Entry<String, String> e2 = iter2.next();
result.put(e1.getKey(), e1.getValue());
result.put(e2.getKey(), e2.getValue());
}
If keys of both the maps are like id1, id2, month1, month2, then you can use a custom Comparator with number for sort as below,
Comparator<Map.Entry<String, String>> comparator = Comparator.comparing(c -> c.getKey().replaceAll("^\\D+", "")) ;
Map<String, String> collect = Stream.concat(map1.entrySet().stream(), map2.entrySet().stream())
.sorted(comparator)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,(oldValue, newValue) -> oldValue, LinkedHashMap::new));
To merge (interleave) the content of two maps, iterate them both in parallel and add to a LinkedHashMap, so the insertion order is retained.
Create mergedMap as a LinkedHashMap
Get iterator1 as an iterator for the entries of map1
Get iterator2 as an iterator for the entries of map2
Repeat until both iterators are at-end:
If not at-end, get next entry from iterator1 and add to mergedMap.
If not at-end, get next entry from iterator2 and add to mergedMap.
That's it. That's all there is to it. Now you just need to write the code doing that.
Related
I have a map, Map<String, Map<String, String>> myMap = new HashMap<>(); that I would like to remap to get it's values, so that I get as a result Map<String, String>.
Is it possible to do the mapping using stream API?
I have solved the problem using a for loop but I'm interested if that could be done using streams.
My solution:
Map<String, String> result = new HashMap<>();
myMap.forEach((k, v) -> {
result.putAll(v);
});
What I want is to get all the values from myMap and put them in a new Map.
If you are certain there are no duplicate keys, you can do it like this.
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
If there may be duplicate keys between the inner maps, you will have to introduce merge function to resolve conflicts. Simple resolution keeping the value of the second encountered entry may look like this:
Map<String, String> res = myMap.values()
.stream()
.flatMap(value -> value.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2));
Basically, stream the values, which are Maps, flatten them to a stream of entries and collect the entries in a new Map.
You need to flatten the entries of the nested maps which can be done using either flatMap() or mapMulty().
And then apply collect() with the minimalistic two-args flavor of Collector toMap() passed as an argument. It would be sufficient since you don't expect duplicates.
Here's an example using flatMap():
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.flatMap(entry -> entry.getValue().entrySet().stream()) // stream of map entries
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));
Example with Java 16 mapMulti() used for flattening the data:
Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> res = myMap.entrySet().stream() // stream of maps
.<Map.Entry<String, String>>mapMulti((entry, consumer) ->
entry.getValue().entrySet().forEach(consumer) // stream of map entries
)
.collect(Collectors.toMap(
Map.Entry::getKey, // key mapper
Map.Entry::getValue // value mapper
));
HashMap<String, String> map = new HashMap<String, String>();
HashMap<String, String> newMap = new HashMap<String, String>();
map.put("A","1");
map.put("B","2");
map.put("C","2");
map.put("D","1");
Expected Output: "AD", "1" and "BC", "2" present inside the newMap which means, if the data values were same it needs combine its keys to have only one data value by combining its keys inside the newMap created how to achieve this in Java?
You want to group by the "integer" value using Collectors.groupingBy and collect the former keys as a new value. By default, grouping yields in List. You can further use downstream collector Collectors.mapping and another downstream collector Collectors.reducing to map and concatenate the individual items (values) as a single String.
Map<String, String> groupedMap = map.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.reducing("", (l, r) -> l + r))));
{1=AD, 2=BC}
Now, you can switch keys with values for the final result, though I really think you finally need what is already in the groupedMap as further processing might cause an error on duplicated keys:
Map<String, String> newMap = groupedMap.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey));
{BC=2, AD=1}
It is possible, put it all together using Collectors.collectingAndThen (matter of taste):
Map<String, String> newMap = map.entrySet().stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.reducing("", (l, r) -> l + r))),
m -> m.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getValue,
Map.Entry::getKey))));
Based on logic:
Loop through your map
For each value, get the corresponding key from the new map (based on the value)
If the new map key exists, remove it and put it again with the extra letter at the end
If not exists, just put it without any concatenation.
for (var entry : map.entrySet())
{
String newMapKey = getKey(newMap, entry.getValue());
if (newMapKey != null)
{
newMap.remove(newMapKey);
newMap.put(newMapKey + entry.getKey(), entry.getValue());
continue;
}
newMap.put(entry.getKey(), entry.getValue());
}
The extra method:
private static String getKey(HashMap<String, String> map, String value)
{
for (String key : map.keySet())
if (value.equals(map.get(key)))
return key;
return null;
}
{BC=2, AD=1}
Using Java 8
You can try the below approach in order to get the desired result.
Code:
public class Test {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
Map<String, String> newMap;
map.put("A","1");
map.put("B","2");
map.put("C","2");
map.put("D","1");
Map<String, String> tempMap = map.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey,Collectors.joining(""))));
newMap = tempMap.entrySet().stream().sorted(Map.Entry.comparingByValue())
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey,(a,b) -> a, LinkedHashMap::new));
System.out.println(newMap);
}
}
Output:
{AD=1, BC=2}
If you want the keys of the source map to be concatenated in alphabetical order like in your example "AD", "BC" (and not "DA" or "CB"), then you can ensure that by creating an intermediate map of type Map<String,List<String>> associating each distinct value in the source map with a List of keys. Then sort each list and generate a string from it.
That how it might be implemented:
Map<String, String> map = Map.of(
"A", "1", "B", "2","C", "2","D", "1"
);
Map<String, String> newMap = map.entrySet().stream()
.collect(Collectors.groupingBy( // intermediate Map<String, List<String>>
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList())
))
.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getValue().stream().sorted().collect(Collectors.joining()),
Map.Entry::getKey
));
newMap.forEach((k, v) -> System.out.println(k + " -> " + v));
Output:
BC -> 2
AD -> 1
So I have a Map<String, Map<String, List<Person>>> and I want to transform it to Map<Integer, Map<String, Integer>> where the first Integer key represents numbers 1,2,3.....(the index of the current entry taken from the first map) and the second Integer gives me the size of the List<Person>.
The String in the inner map remains the same....
First I just tried to replace the second Integer in the map with the size of the list but it does not work.....
Map<String, Map<String, List<Person>>> map2 =...
Map<String, Map<String, Integer>> map = map2.entrySet().stream().collect(
Collectors.groupingBy(p -> p.getKey(), Collectors.toMap(p -> p.getValue().getKey(), p->p.getValue().values().size())));
By definition, a map doesn't have an index. You're probably trying to give the entries of the map a number starting from 1 based on the insertion order. If you're using a LinkedHashMap there's a way to accomplish this.
However, a HashMap makes no guarantees as to the order of the map. Also, it does not guarantee that the order will remain constant over time; So, giving these entries a number will not always yield the expected outcome.
That said, you might only want to number the entries in the map from 1 to n where n is the size of the map and are not concerned in the insertion order of entries as mentioned above i.e. the first inserted entry doesn't necessarily have to be numbered as 1 and the second inserted entry doesn't necessarily have to be numbered as 2 etc..
Now, to the solution:
You can have a List<Map<String, Integer>> as a temporary result set and when you need the numbering of the entries simply loop over the elements and get the numbering with i + 1 where i is the control variable.
example assuming we have this list:
List<Map<String, Integer>> temporaryResult = myMap.values()
.stream()
.map(a -> a.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
c -> c.getValue().size()))
)
.collect(Collectors.toList());
we can then get a Map<Integer, Map<String, Integer>> with:
Map<Integer, Map<String, Integer>> resultSet =
IntStream.range(0, temporaryResult.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<>(i + 1,
temporaryResult.get(i)))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue));
Note -
If the numbering should be based on insertion order this code will only yield the expected outcome if the map you're using maintains insertion order.
If the numbering is not necessarily based on insertion order of the entries in the map then this solution will work regardless of the map implementation being used.
I'd use a list to keep the order of the entries from the initial map, no need to store an index as a key of the map.
This will go through all the values of your initial map, map the Map to the one awaited with the size() of the list and collect everything into a List
List<Map<String, Integer>> map =
map2.values()
.stream()
.map(m-> m.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())))
.collect(Collectors.toList());
If you really need a Map for any reason, the following code should work too
int i = 1;
Map<Integer, Map<String, Integer>> newMap = new HashMap<>();
for (Map.Entry entry : map2.entrySet()) {
Map<String, Integer> map = entry.value().entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())
newMap.put(i++, map);
}
I have Map<String, Object> which has to be become Map<String, String>. Filtering should be done by List<String>.
That list contains keys of map elements that should be in new map.
For this I need to use streams.
Map<String, Object> oldMap;
List<String> keysForFiltering;
Map<String, String> newMap;
It would be more efficient if the filter would operate on a Set of keys instead of a List of keys, since searching a Set is more efficient than searching a List.
Map<String, String> newMap =
oldMap.entrySet()
.stream()
.filter(e -> keysForFiltering.contains(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().toString()));
since you have a map then you can get the stream of that and use a custom predicate, that predicate need to check if the Entry.key is present in your list or not
Map<String, String> myMap = new HashMap<>();
myMap.put("A", "fortran");
myMap.put("B", "java");
myMap.put("C", "c++");
myMap.put("D", "php");
List<String> keysForFiltering = Arrays.asList("A", "C");
Predicate<Entry<String, String>> myPredicate = t -> keysForFiltering.contains(t.getKey());
Map<String, String> filteredMap = myMap
.entrySet().stream().filter(myPredicate)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
System.out.println(filteredMap);
I have this peace of code working fine, that takes a Map<String, List<Device>> and sort time and return the same datastructure:
Stream<Map.Entry<String, List<Device>>> results = device.getDeviceMapList().entrySet().stream();
Map<String, List<Device>> sortedMap = new HashMap<String, List<Device>>();
results.forEach(e -> {
e.getValue().sort(Comparator.comparing(Device::getStationTimeStamp));
sortedMap.put(e.getKey(), e.getValue());
});
Now I tried to use Collectors.toMap and did not successes:
Map<String, List<Device>> sortedMap = results.forEach(e -> {
e.getValue().stream()
.sorted(Comparator.comparing(Device::getStationTimeStamp))
.collect(Collectors.toMap(e.getKey(), ArrayList<Device>::new));
});
The part .collect(Collectors.toMap(e.getKey(), ArrayList<Device>::new)); is what I tried and it is not fully correct, what I have done wrong?
To reproduce your problem I have created an example Map
Map<Integer, List<Integer>> mapA = new HashMap<>();
mapA.put(1, Arrays.asList(1,2,3,4,5,8,7,6,9));
mapA.put(2, Arrays.asList(1,2,3,5,4,6,7,8,9));
mapA.put(3, Arrays.asList(2,3,1,4,5,6,7,8,9));
mapA.put(4, Arrays.asList(1,2,8,4,6,5,7,3,9));
mapA.put(5, Arrays.asList(9,2,3,4,5,6,7,8,1));
and turned this into a Stream similar to yours
Stream<Map.Entry<Integer, List<Integer>>> results = mapA.entrySet().stream();
As you may have noticed, the Lists in mapA are not sorted.
To get a Map<Integer,List<Integer>> with the List sorted, you can do the following
Map<Integer,List<Integer>> sortedMap =
results.collect(Collectors.toMap(s -> s.getKey(),
s -> s.getValue().stream()
.sorted(Comparator.naturalOrder()).collect(Collectors.toList())));
You will have to replace the Comparator.naturalOrder() with Comparator.comparing(Device::getStationTimeStamp).