How to get a Set from a HashMap within a HashMap - java

I am trying to create a Set and Collection from a HashMap that is within another HashMap. The problem is that the getKey() method is not applicable.
HashMap<HashMap<String, String>, String> list = getList(issues);
Set <String> set1 = list.getKey().keySet();
Collection <String> set2 = list.getKey().values();

From your collection, to get all the keys of each entry, you have to loop over each entry and get the keys, to do that using Java-8+, you can use:
Set<String> allKeys = list.entrySet().stream()
.flatMap(e -> e.getKey().keySet().stream())
.collect(Collectors.toSet());
To get the value, you can do it like this:
List<String> allValues = list.entrySet().stream()
.flatMap(e -> e.getKey().values().stream())
.collect(Collectors.toList());

Get Keys
//Get Keys of childHashMap
Set<String> keys = parent.keySet().stream().
flatMap(child -> child.keySet().stream()).
collect(Collectors.toSet());
1.Stream on keySet of parent hashMap (Iterate on child hashmap)
parent.keySet().stream()
2.Return keySet of child hashmap (it's like {1},{2,3},{4,5})
child -> child.keySet().stream()
3.Flat the previous step Set (it's like convert {1},{2,3},{4,5} to {1,2,3,4,5})
flatMap(child -> child.keySet().stream()).
4.Return keys
collect(Collectors.toSet());
Get Values
//Get Values of childHashMap
Collection<String> values = parent.keySet().stream()
.map(HashMap::values)
.flatMap(Collection::stream)
.collect(Collectors.toList());
1.Stream keySet of parent hashMap (Iterate on child hashmap)
parent.keySet().stream()
2.Return values of the child hashMap (its like : {1},{2},{3})
map(HashMap::values)
3.Use of flatMap to flat the previous step (Convert {1},{2},{3} to {1,2,3})
flatMap(Collection::stream)
4.Collect values to a List
collect(Collectors.toList())
Sample
//Parent
HashMap<HashMap<String, String>, String> parent = new HashMap<>();
//Child HashMap 1
HashMap<String, String> childHashMap = new HashMap<>();
childHashMap.put("a", "b");
//Child HashMap2
HashMap<String, String> childHashMap2 = new HashMap<>();
childHashMap2.put("d", "e");
//Parent init
parent.put(childHashMap, "c");
parent.put(childHashMap2, "f");
//Get Keys
Set<String> keys = parent.keySet().stream().flatMap(child -> child.keySet().stream()).collect(Collectors.toSet());
//Get values
Collection<String> values = parent.keySet().stream().map(HashMap::values).flatMap(Collection::stream).collect(Collectors.toList());
//Print key and values
keys.forEach(System.out::println);
values.forEach(System.out::println);
Result =
keys = a ,d
values = e , b

Related

delete from parent hashmap from a nested hashmap condition

I have the following structure
HashMap<String, HashMap<String, String>> h = new HashMap<>();
HashMap<String, String>> h1 = new HashMap<>();
h1.put("key10", "value10")
h1.put("key11", "value11")
h1.put("date", "2018-10-18T00:00:57.907Z")
h.put("1#100", h1)
HashMap<String, String>> h2 = new HashMap<>();
h2.put("key20", "value20")
h2.put("key21", "value21")
h2.put("date", "2023-02-03T10:00:00.907Z")
h.put("2#000", h2)
Imagine I have many entries like the examples above.
In certain moment (scheduler) i have this requirement:
check all nested hash maps (for each/stream)
see if date condition is true
find parent key and delete from main hash map
In this exemple the final hash map will be
h2.put("key20", "value20")
h2.put("key21", "value21")
h2.put("date", "2023-02-03T10:00:00.907Z")
h.put("2#000", h2)
h2 => {key20 => value20, key21 => value21, date => 2023-02-03T10:00:00.907Z}
i have this code right now
h.forEach((k,v) -> {
v.entrySet()
.stream()
.filter(e -> e.getKey().equals("date"))
.filter(t -> Timestamp.from(Instant.now()).getTime() - Timestamp.valueOf(t.getValue()).getTime() > milisDiff)
//need now to access parent and delete with by k key
Can do in one step (lambda) or i need to have extra structure to collect parent keys and after proceed to delete within for each ?
This may do what you want. Just filter out bad elements and assign to the same map.
HashMap<String, HashMap<String, String>> h = new HashMap<>();
HashMap<String, String> h1 = new HashMap<>();
h1.put("key10", "value10");
h1.put("key11", "value11");
h1.put("date", "2018-10-18T00:00:57.907Z");
h.put("1#100", h1);
HashMap<String, String> h2 = new HashMap<>();
h2.put("key20", "value20");
h2.put("key21", "value21");
h2.put("date", "2023-02-04T10:00:00.907Z");
h.put("2#000", h2);
// any instant after `now` will pass the filter and be put in the map
Predicate<String> check = str -> Instant.parse(str)
.isAfter(Instant.now());
h = h.entrySet().stream()
.filter(e -> check.test(e.getValue().get("date")))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue,
(a,b)->a,
HashMap::new));
h.values().forEach(m -> {
m.entrySet().forEach(System.out::println);
});
prints
date=2023-02-04T10:00:00.907Z
key21=value21
key20=value20
My predicate simply deleted the date if it expired. Yours was a tighter threshold.
Updated
Here is another option in case building a new map takes too long. It uses an iterator to run thru the loop and modify the existing map by removing Maps with old dates.
Iterator<Entry<String,Map<String,String>>> it = h.entrySet().iterator();
while (it.hasNext()) {
Entry<String,Map<String, String>> e = it.next();
if (!check.test(e.getValue().get("date"))) {
it.remove();
}
}

Inititialize Map keys from list with a default value

I need to initialize the keys of a map from a list and give a default value for each entry.
Currently I use a for loop :
Map<String, String > myMap = new HashMap<>();
List<String> keys = Arrays.asList("a", "b", "c", "d");
for (String key : keys){
myMap.put(key, "default");
}
Is there a cleaner way to do that ? Stream or lambda maybe ?
Yes, you can create a Stream of that List's elements and collect them to a Map:
Map<String,String> map = keys.stream ()
.collect (Collectors.toMap (Function.identity (),
k -> "default"));

Merge two maps in specific order

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.

How to sort a Map by Value's nested value (ArrayList) in Java?

I have the following Map:
Map<Long, List<Address>> map = new HashMap<Long, List<Address>>();
which is filled with pairs of keys and values.
For example: key = student id and
value = list of Address.
In Address object I have country name(String).
I want to sort the total map by the country name. I have tried many ways but not getting the Idea. Any ideas?
Below is my tried code.
private static Map<Long, List<Address>> sortByValue(Map<Long, List<Address>> unsortMap) {
// Convert Map to List of Map
List<Map.Entry<Long, List<Address>>> unSortedList =
new ArrayList<Map.Entry<Long, List<Address>>>(unsortMap.entrySet());
// sort the List
Collections.sort(unSortedList, new Comparator<Map.Entry<Long, List<Address>>>() {
public int compare(Map.Entry<Long, List<Address>> object1,
Map.Entry<Long, List<Address>> object2) {
// sort by country name
return ???;
}
});
// Loop the sorted list and put it into a new insertion order Map LinkedHashMap
Map<Long, List<Address>> sortedMap = new LinkedHashMap<Long, List<Address>>();
for (Map.Entry<Long, List<Address>> entry : unSortedList) {
sortedMap.put(entry.getKey(), entry.getValue());
}
return sortedMap;
}
You can create a temporary TreeMap inside the method and store the reverse mappings (i.e. country -> keys) into it. Once done, you can iterate over it and fill the values in the result, e.g.:
public static Map<Long, List<Address>> sort(Map<Long, List<Address>> map){
//Create temporary map, sorted by countries
Map<String, List<Long>> countryMap = new TreeMap<>();
map.entrySet().stream()
.forEach(e -> {
e.getValue()
.stream()
.map(a -> a.country)
.forEach(c -> countryMap.computeIfAbsent(c, k -> new ArrayList<Long>()).add(e.getKey()));
});
//Iterate over treemap and populate the values in result
Map<Long, List<Address>> sortedMap = new LinkedHashMap<>();
countryMap.entrySet()
.stream()
.flatMap(e -> e.getValue().stream())
.forEach(k -> sortedMap.put(k, map.get(k)));
return sortedMap;
}

Merge Map<String, List<String> Java 8 Stream

I would like to merge two Map with JAVA 8 Stream:
Map<String, List<String>> mapGlobal = new HashMap<String, List<String>>();
Map<String, List<String>> mapAdded = new HashMap<String, List<String>>();
I try to use this implementation:
mapGlobal = Stream.of(mapGlobal, mapAdded)
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue,
Collectors.toList())
));
However, this implementation only create a result like:
Map<String, List<Object>>
If one key is not contained in the mapGlobal, it would be added as a new key with the corresponding List of String. If the key is duplicated in mapGlobal and mapAdded, both list of values will be merge as: A = {1, 3, 5, 7} and B = {1, 2, 4, 6} then A ∪ B = {1, 2, 3, 4, 5, 6, 7}.
You can do this by iterating over all the entries in mapAdded and merging them into mapGlobal.
The following iterates over the entries of mapAdded by calling forEach(action) where the action consumes the key and value of each entry. For each entry, we call merge(key, value, remappingFunction) on mapGlobal: this will either create the entry under the key k and value v if the key didn't exist or it will invoke the given remapping function if they already existed. This function takes the 2 lists to merge, which in this case, are first added to a TreeSet to ensure both unique and sorted elements and converted back into a list:
mapAdded.forEach((k, v) -> mapGlobal.merge(k, v, (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));
If you want to run that potentially in parallel, you can create a Stream pipeline by getting the entrySet() and calling parallelStream() on it. But then, you need to make sure to use a map that supports concurrency for mapGlobal, like a ConcurrentHashMap.
ConcurrentMap<String, List<String>> mapGlobal = new ConcurrentHashMap<>();
// ...
mapAdded.entrySet().parallelStream().forEach(e -> mapGlobal.merge(e.getKey(), e.getValue(), (v1, v2) -> {
Set<String> set = new TreeSet<>(v1);
set.addAll(v2);
return new ArrayList<>(set);
}));
Using foreach over Map can be used to merge given arraylist.
public Map<String, ArrayList<String>> merge(Map<String, ArrayList<String>> map1, Map<String, ArrayList<String>> map2) {
Map<String, ArrayList<String>> map = new HashMap<>();
map.putAll(map1);
map2.forEach((key , value) -> {
//Get the value for key in map.
ArrayList<String> list = map.get(key);
if (list == null) {
map.put(key,value);
}
else {
//Merge two list together
ArrayList<String> mergedValue = new ArrayList<>(value);
mergedValue.addAll(list);
map.put(key , mergedValue);
}
});
return map;
}
The original implementation doesn't create result like Map<String, List<Object>>, but Map<String, List<List<String>>>. You need additional Stream pipeline on it to produce Map<String, List<String>>.
Map<String, List<String>> result = new HashMap<String, List<String>>();
Map<String, List<String>> map1 = new HashMap<String, List<String>>();
Map<String, List<String>> map2 = new HashMap<String, List<String>>();
for(Map.Entry<String, List<String>> entry: map1.entrySet()) {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
for(Map.Entry<String, List<String>> entry: map2.entrySet()) {
if(result.contains(entry.getKey())){
result.get(entry.getKey()).addAll(entry.getValue());
} else {
result.put(entry.getKey(), new ArrayList<>(entry.getValue());
}
}
This solution creates independent result map without any reference to map1 and map2 lists.
Using StreamEx
Map<String, List<String>> mergedMap =
EntryStream.of(mapGlobal)
.append(EntryStream.of(mapAdded))
.toMap((v1, v2) -> {
List<String> combined = new ArrayList<>();
combined.addAll(v1);
combined.addAll(v2);
return combined;
});
If you have even more maps to merge just append to the stream
.append(EntryStream.of(mapAdded2))
.append(EntryStream.of(mapAdded3))
Here is the full code to Iterate Two HashMap which has values stored in the form of a list. Merging all the keys and values in first hashmap. Below is the example.
HashMap<String, List<String>> hmap1 = new HashMap<>();
List<String> list1 = new LinkedList<>();
list1.add("000");
list1.add("111");
List<String> list2 = new LinkedList<>();
list2.add("222");
list2.add("333");
hmap1.put("Competitor", list1);
hmap1.put("Contractor", list2);
// System.out.println(hmap1);
HashMap<String, List<String>> hmap2 = new HashMap<>();
List<String> list3 = new LinkedList<>();
list3.add("aaa");
list3.add("bbb");
List<String> list4 = new LinkedList<>();
list4.add("ccc");
list4.add("ddd");
hmap2.put("Competitor", list3);
hmap2.put("Contractor", list4);
//******* Java 8 Feature *****
hmap1.forEach((k, v) -> hmap2.merge(k, v, (v1, v2) -> {
List<String> li = new LinkedList<>(v1);
li.addAll(v2);
hmap2.put(k,li);
return new ArrayList<>(li);
}));
System.out.println(hmap2);
Output:
{Competitor=[aaa, bbb, 000, 111], Contractor=[ccc, ddd, 222, 333]}

Categories

Resources