Seeking Optimization Suggestions for Map-to-Map Conversion - java

I'm seeking feedback as to whether there's a more efficient approach than what I'm doing in my code shown at the bottom.
Basically, given this map:
Set<String> A_Set = new HashSet<>(Arrays.asList("1111", "2222", "5555"));
Set<String> B_Set = new HashSet<>(Arrays.asList("3333", "4444"));
Set<String> C_Set = new HashSet<>(Arrays.asList("6666"));
Set<String> D_Set = new HashSet<>(Arrays.asList("2222", "5555", "6666"));
Map<String, Set<String>> values = new HashMap<>();
values.put("A", A_Set);
values.put("B", B_Set);
values.put("C", C_Set);
values.put("D", D_Set);
which looks like this:
How do I create a Map<String, List<Boolean> map such that it looks like this:
In the most efficient way possible. My real Map has thousands of values per Set, but there are only ever 4 Sets (A, B, C, D).
Here's my current code. Can you think of a more efficient approach?
import java.util.*;
public class MapToMap {
public static void main(String[] args) {
Set<String> A_Set = new HashSet<>(Arrays.asList("1111", "2222", "5555"));
Set<String> B_Set = new HashSet<>(Arrays.asList("3333", "4444"));
Set<String> C_Set = new HashSet<>(Arrays.asList("6666"));
Set<String> D_Set = new HashSet<>(Arrays.asList("2222", "5555", "6666"));
Map<String, Set<String>> values = new HashMap<>();
values.put("A", A_Set);
values.put("B", B_Set);
values.put("C", C_Set);
values.put("D", D_Set);
Map<String, List<Boolean>> exists = new HashMap<>();
for (Map.Entry<String, Set<String>> v : values.entrySet()) {
for (String val : v.getValue()) {
if (exists.containsKey(val)) {
List<Boolean> list = exists.get(val);
list = addValue(v.getKey(), list);
exists.put(val, list);
} else {
List<Boolean> newList = new ArrayList<>(Arrays.asList(false, false, false, false));
newList = addValue(v.getKey(), newList);
exists.put(val, newList);
}
}
}
for (Map.Entry<String, List<Boolean>> s : exists.entrySet()) {
System.out.println(s);
}
}
private static List<Boolean> addValue(String key, List<Boolean> listToUse) {
List<Boolean> newList = new ArrayList<>();
if (Objects.equals("A", key)) {
newList.addAll(Arrays.asList(true, listToUse.get(1), listToUse.get(2), listToUse.get(3)));
} else if (Objects.equals("B", key)) {
newList.addAll(Arrays.asList(listToUse.get(0), true, listToUse.get(2), listToUse.get(3)));
} else if (Objects.equals("C", key)) {
newList.addAll(Arrays.asList(listToUse.get(0), listToUse.get(1), true, listToUse.get(3)));
} else if (Objects.equals("D", key)) {
newList.addAll(Arrays.asList(listToUse.get(0), listToUse.get(1), listToUse.get(2), true));
}
return newList;
}
}

Here's a solution using streams:
Map<String, List<Boolean>> exists = values.values()
.stream()
.flatMap(Set::stream)
.distinct()
.collect(Collectors.toMap(v -> v, v -> Stream.of("A", "B", "C", "D")
.map(k -> values.get(k).contains(v))
.collect(Collectors.toList())));
Ideone Demo

Related

How to convert Map<Key1Type, Val1Type> to Map<Key1Type, Val2Type>

Want to convert as per the method signature below.
Map<A, Set<B>> convert(Map<A, List<B>> original){
original.entrySet().stream().map(entry -> {
return new SimpleImmutableEntry(entry.getKey(), new HashSet<>(entry.getValue()));
}).collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
}
Its giving me compiling issue not sure why. All am doing is transforming entry to intermediate entry of Map.Entry<String, Set<String>> and than collecting.
A simplier definition skips intermediate map step as you can convert each Map.Entry<A, List<B>> into Map.Entry<A, Set<B>> inside the collect step:
Map<A, Set<B>> convert(Map<A, List<B>> original){
return original.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> new HashSet<>(e.getValue())));
}
You could prefix the method convert with static <A,B> to ensure that it applies to any types A and B.
static <A,B> Map<A, Set<B>> convert(Map<A, List<B>> original){
...
}
package com.student;
import java.util.*;
import java.util.stream.Collectors;
public class MapConversion {
static Map < String, Set < String >> convert(Map < String, List < String >> map1) {
return map1.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e - > new HashSet(e.getValue())));
}
public static void main(String[] args) {
List < String > list1 = Arrays.asList(new String[] {
"a",
"b"
});
Map < String, List < String >> map1 = new HashMap < > () {
{
put("strin1", Arrays.asList(new String[] {
"a",
"a"
}));
put("strin2", Arrays.asList(new String[] {
"d",
"d"
}));
put("strin3", Arrays.asList(new String[] {
"e",
"f"
}));
}
};
System.out.println(convert(map1));
}
}

Using ComputeIfAbsent & ComputeIfPresent to put list into map

I have a blok of code that's work well for me :
for (String word : distinctWordsInOneLigne) {
Map<String, List<Integer>> map = new HashMap<>();
if (!word.isEmpty()) {
List<Integer> linePositionsOfWord = new LinkedList<>();
if (currentLine.contains(word)) {
linePositionsOfWord.add(numLine);
if (mapAllWordsPositionInFilesInFolder.containsKey(word)) {
Map<String, List<Integer>> mapList = mapAllWordsPositionInFilesInFolder.get(word);
if (mapList.containsKey(filePath)) {
List<Integer> list = mapList.get(filePath);
list.add(numLine);
} else {
mapList.put(filePath, linePositionsOfWord);
}
} else {
map.put(filePath, linePositionsOfWord);
mapAllWordsPositionInFilesInFolder.put(word, map);
}
}
}
}
NB: Map<String, Map<String, List<Integer>>> mapAllWordsPositionInFilesInFolder = new HashMap<>();
The result of i somethinglike this :
{word1={file2.txt=[7], file1.txt=[1, 2]}, word2={file2.txt=[1, 2, 9, 13], file5.txt=[2, 3, 9]}}
Now i want have some result but now by using ComputeIfAbsent & ComputeIfPresent instead of containsKey and all this if ... else.
I tried this but not work :
mapAllWordsPositionInFilesInFolder.computeIfAbsent(word,v -> new HashMap<>())
.computeIfAbsent(filePath, val -> linePositionsOfWord);
mapAllWordsPositionInFilesInFolder.computeIfPresent(word,(k,v)->{
v.computeIfPresent(filePath, (x, y) -> linePositionsOfWord.add(numLine));
return v;
});
I need help please ! thank's :)
You wouldn't use computeIfPresent() for this, but you would use computeIfAbsent() like this:
for (String word : distinctWordsInOneLigne) {
if (! word.isEmpty() && currentLine.contains(word)) {
mapAllWordsPositionInFilesInFolder.computeIfAbsent(word, k -> new HashMap<>())
.computeIfAbsent(filePath, k -> new LinkedList<>())
.add(numLine);
}
}
The original code was very badly written. Even without using computeIfPresent(), it can be cleaned up a lot, eliminating repeated code. This is how it should have been written:
for (String word : distinctWordsInOneLigne) {
if (! word.isEmpty() && currentLine.contains(word)) {
Map<String, List<Integer>> mapList = mapAllWordsPositionInFilesInFolder.get(word);
if (mapList == null) {
mapList = new HashMap<>();
mapAllWordsPositionInFilesInFolder.put(word, mapList);
}
List<Integer> linePositionsOfWord = mapList.get(filePath);
if (linePositionsOfWord == null) {
linePositionsOfWord = new LinkedList<>();
mapList.put(filePath, linePositionsOfWord);
}
linePositionsOfWord.add(numLine);
}
}
With inlining, that can be reduced to:
for (String word : distinctWordsInOneLigne) {
if (! word.isEmpty() && currentLine.contains(word)) {
Map<String, List<Integer>> mapList = mapAllWordsPositionInFilesInFolder.get(word);
if (mapList == null)
mapAllWordsPositionInFilesInFolder.put(word, mapList = new HashMap<>());
List<Integer> linePositionsOfWord = mapList.get(filePath);
if (linePositionsOfWord == null)
mapList.put(filePath, linePositionsOfWord = new LinkedList<>());
linePositionsOfWord.add(numLine);
}
}

How to flatten an array of dictionaries in Java?

I am working on below problem where I need to flatten array of dicts:
For example- Below is an input:
[
{'a':
{'b':
{'c':
{'d':'e'}
}
}
},
{'a':{'b':{'c':{'d':{'e':'f'}}}}},
{'a':'b'}
]
And the output will be:
[
{'a_b_c_d':'e'},
{'a_b_c_d_e':'f'},
{'a':'b'}
]
Below is what I was able to come up with. Is there any better way to solve this problem?
private static List<Map<String, String>> flatDictionary(final List<Map<String, Object>> input) {
List<Map<String, String>> listHolder = new ArrayList<>();
if(input == null || input.isEmpty()) {
return listHolder;
}
for(Map<String, Object> mapHolder : input) {
Map<String, String> m = new HashMap<>();
StringBuilder sb = new StringBuilder();
Map<String, String> output = helper(mapHolder, sb, m);
listHolder.add(output);
}
return listHolder;
}
private static Map<String, String> helper(final Map<String, Object> map, final StringBuilder sb, final Map<String, String> output) {
String mapValue = null;
for(Map.Entry<String, Object> holder : map.entrySet()) {
String key = holder.getKey();
Object value = holder.getValue();
if(value instanceof Map) {
sb.append(key).append("_");
helper((HashMap<String, Object>) value, sb, output);
} else if(value instanceof String) {
sb.append(key);
mapValue = (String) value;
}
output.put(sb.toString(), mapValue);
}
return output;
}
I would use recursion.
First define a method to flatten a single Map
public static void flatten(final String keyPrefix, final Map<String, Object> input, final Map<String, Object> output) {
for (final Map.Entry<String, Object> e : input.entrySet()) {
final var key = keyPrefix.isBlank() ? e.getKey() : keyPrefix + "_" + e.getKey();
if (e.getValue() instanceof Map) {
// if the nested Map is of the wrong type bad things may happen
flatten(key, (Map<String, Object>) e.getValue(), output);
} else {
output.put(key, e.getValue());
}
}
}
NB: This makes no attempt to deal with duplicate keys.
Usage:
public static void main(final String[] args) throws InterruptedException {
final var data = Map.of(
"A", Map.of("a", "Expect A_a"),
"B", Map.of("b1", Map.of(
"bb1", "expect B_b1_bb1",
"bb2", "expect B_b1_bb2"
)),
"C", "Expect C");
final var output = new HashMap<String, Object>();
flatten("", data, output);
output.forEach((k, v) -> System.out.printf("%s -> %s%n", k, v));
}
Output:
C -> Expect C
A_a -> Expect A_a
B_b1_bb2 -> expect B_b1_bb2
B_b1_bb1 -> expect B_b1_bb1
Now, simply define a method that loops to take your List
public static final Map<String, Object> flattenAll(final List<Map<String, Object>> input) {
final var output = new HashMap<String, Object>();
input.forEach(map -> flatten("", map, output));
return output;
}
NB: This makes no attempt to deal with duplicate keys.

Grouping and Double sorting List<Person>

I went through all the manuals out there and all SO questions but still unable to figure this out...
I have a List (integer represents age):
List<Person> people = Arrays.asList
(
new Person("bob", 10),
new Person("sue", 4),
new Person("tom", 37),
new Person("jim", 10),
new Person("boo", 4),
new Person("ekk", 53),
new Person("joe", 10)
);
I need to:
group the list by age,
sort by group sizes (descending),
sort by age (descending)
So using the example above the result would have to be like this:
{10=[bob, jim, joe],4=[sue, boo], 53=[ekk], 37=[tom]}
What I tried:
I tried with and without streams. I failed on both.
Note: I would lean toward no stream solution, because from my testing of the below code it seems like streams are much slower (I used System.nanotime()). These 3 operations will be done thousands of times each time, so it may make a slight difference.
Using streams here is what I did:
List<List<Person>> grpd = new ArrayList<>
(
people.stream()
.collect
(
groupingBy(Person::getAge, toList())
)
.values()
);
grpd = grpd.stream().sorted((a, b) -> Integer.compare(b.size(), a.size())).collect(toList());
No streams approach:
Map<Integer, List<Person>> grouped = new HashMap<>();
for (Person person : people)
{
if (grouped.containsKey(person._age))
{
grouped.get(person._age).add(person);
} else
{
List<Person> p = new ArrayList<>();
p.add(person);
grouped.put(person._age, p);
}
}
List<Map.Entry<Integer, List<Person>>> entries = new ArrayList<>(grouped.entrySet());
Collections.sort(entries, new Comparator<Map.Entry<Integer, List<Person>>>()
{
#Override
public int compare(Map.Entry<Integer, List<Person>> o1, Map.Entry<Integer, List<Person>> o2)
{
return Integer.compare(o2.getValue().size(), o1.getValue().size());
}
});
Map<Integer, List<Person>> sortedBySize = new LinkedHashMap<>();
for (Map.Entry<Integer, List<Person>> entry : entries)
{
sortedBySize.put(entry.getKey(), entry.getValue());
}
Problem:
I have no idea how to add the final sort on either case.
public class Person
{
public String _name;
public int _age;
public int getAge() { return _age; }
public Person(String name, int age)
{
_name = name;
_age = age;
}
#Override
public String toString()
{
return _name;
}
}
Use streams.
First, group them by age:
Map<Integer, List<Person>> groupedByAge =
people.stream().collect(groupingBy(Person::getAge));
Then sort the entries of this map:
Comparator<Map.Entry<Integer, List<Person>>> byCount = comparingInt(e -> e.getValue().size());
Comparator<Map.Entry<Integer, List<Person>>> byAge = comparingInt(Map.Entry::getKey);
Stream<Map.Entry<Integer, List<Person>>> sorted =
groupedByAge.entrySet().stream().sorted(byCount.reversed().thenComparing(byAge.reversed()));
Then just get the list out of there:
List<List<Person>> result = sorted.map(Map.Entry::getValue).collect(toList());
(You can put this all into a single expression, but I claim it is more readable broken out like this).
As you've also asked about a non-stream solution, here it is:
Map<Integer, List<Person>> grouped = new HashMap<>();
people.forEach(person -> grouped.computeIfAbsent(
person.getAge(),
k -> new ArrayList<>())
.add(person));
This groups by age. Now let's sort the entries, first by group size descending, then by age descending:
List<Map.Entry<Integer, List<Person>>> toSort = new ArrayList<>(grouped.entrySet());
toSort.sort(
Comparator.comparingInt((Map.Entry<Integer, List<Person>> e) -> e.getValue().size())
.reversed()
.thenComparingInt(Map.Entry.comparingByKey().reversed()));
Now, toSort is a sorted list of entries. You need to put those entries into a new map:
Map<Integer, List<Person>> sorted = new LinkedHashMap<>();
toSort.forEach(e -> sorted.put(e.getKey(), e.getValue()));
And sorted holds the result you want.
Since you were also looking for a non-stream solution:
public static Map<Integer, List<Person>> group(List<Person> people) {
Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
for (Person person : people) {
intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
}
Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();
List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));
Map<Integer, List<Person>> result = new LinkedHashMap<>(entries.size());
for (Entry<Integer, List<Person>> entry : entries) {
result.put(entry.getKey(), entry.getValue());
}
return result;
}
Or if you prefer the result to be a List<List<Person>>:
public static List<List<Person>> group(List<Person> people) {
Map<Integer, List<Person>> intermediateGrouping = new HashMap<>();
for (Person person : people) {
intermediateGrouping.computeIfAbsent(person.getAge(), k -> new ArrayList<>()).add(person);
}
Comparator<Entry<Integer, List<Person>>> byGroupSize = Entry.comparingByValue(Comparator.comparingInt(List::size));
Comparator<Entry<Integer, List<Person>>> byAge = Entry.comparingByKey();
List<Entry<Integer, List<Person>>> entries = new ArrayList<>(intermediateGrouping.entrySet());
entries.sort(byGroupSize.reversed().thenComparing(byAge.reversed()));
List<List<Person>> result = new ArrayList<>(entries.size());
for (Entry<Integer, List<Person>> entry : entries) {
result.add(entry.getValue());
}
return result;
}
Try modifiying your sort comparator using the below implementation when the sizes are equal for a no streams approach
Collections.sort(entries, new Comparator<Map.Entry<Integer, List<Person>>>()
{
#Override
public int compare(Map.Entry<Integer, List<Person>> o1, Map.Entry<Integer, List<Person>> o2)
{
if(o1.getValue().size()<o2.getValue().size())
return 1;
else if(o1.getValue().size()>o2.getValue().size())
return -1;
else {
if( o1.getKey()< o2.getKey())
return 1;
else if(o1.getKey()>o2.getKey())
return -1;
else
return 0;
}
}
});
Let me know if it works on all your Test Cases

How to sort map or which traversal approach i need to follow?

i just want to get a Sorted map my code is like :
public class SubString {public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("string2");
list.add("STR_str2");
list.add("STR_str3");
getSequesce("STR_str1", list);
List<String> list1 = new ArrayList<>();
list1.add("STR_xyz");
list1.add("STR_ABC");
getSequesce("STR_str2", list1);
List<String> list3 = new ArrayList<>();
list3.add("Anukul");
list3.add("mittal");
getSequesce("STR_str3", list3);
List<String> list4 = new ArrayList<>();
list4.add("Test");
list4.add("STR_XYZ");
getSequesce("STR_ABC", list4);
List<String> list5 = new ArrayList<>();
list5.add("val");
list5.add("var");
getSequesce("STR_XYZ", list5);
List<String> list6 = new ArrayList<>();
list6.add("val6");
list6.add("valtest");
getSequesce("STR_free", list6);
List<String> list7 = new ArrayList<>();
list7.add("val6");
list7.add("STR_free");
getSequesce("STR_7", list7);
}private static void getSequesce(String string, List<String> list) {
Map<String, List<String>> map = new HashMap<>();
Map<String, List<String>> sortedMap = new TreeMap<>();
map.put(string, list);
for (Map.Entry<String, List<String> > itrMap : map.entrySet() ) {
}
}}
In my fist call of getSequence method i have put a string "STR_str1" and a list.
i just want to add this into a map where key is STR_str1 and its value is list.
but my problem is i have to put STR_str2 ,and STR_str3 as key in map before STR_str1. similarly i have to put STR_ABC and STR_XYZ before STR_str2.
i just want a sortedMap from function getSequesce so that i get output like
STR_free,list6
"STR_7", list7
STR_XYZ,list5
STR_ABC,list4
STR_str3,list3
"STR_str2", list1
"STR_str1", list
if value of list start with STR_ then this STR_ must already avail in map.
position of "STR_free", list6 "STR_XYZ", list5 "STR_str3", list3 can be anywhere because they don't contain any dependency.
please help me to suggest what approach i can follow. i have data that will not create cyclic problem.
Thanks.
Map by definition is not sorted (The same as set) so the traversal order is not guaranteed. However there is a interface SortedMap and its implementations (Such as TreeMap). In this case your keys must implement equals() and hashcode() in a meaningful way or implement Comparable interface. In your case you use Strings as keys and String implements comparable. So you can use SortedMap
Thank you for help guys code i was looking for is
private static Map<String, List<String>> map = new LinkedHashMap<String, List<String>>();
private static List<String> nonDependentList = new ArrayList<String>();
private static Map<String, List<String>> dependentMap = new ConcurrentHashMap<String, List<String>>();
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("string2");
list1.add("STR_str2");
list1.add("STR_str3");
addRecord("STR_str1", list1);
List<String> list2 = new ArrayList<>();
list2.add("STR_xyz");
list2.add("STR_ABC");
addRecord("STR_str2", list2);
List<String> list3 = new ArrayList<>();
list3.add("Anukul");
list3.add("mittal");
addRecord("STR_str3", list3);
List<String> list4 = new ArrayList<>();
list4.add("Test");
list4.add("STR_XYZ");
addRecord("STR_ABC", list4);
List<String> list5 = new ArrayList<>();
list5.add("val");
list5.add("var");
addRecord("STR_XYZ", list5);
List<String> list6 = new ArrayList<>();
list6.add("val6");
list6.add("valtest");
addRecord("STR_free", list6);
List<String> list7 = new ArrayList<>();
list7.add("val6");
list7.add("STR_free");
addRecord("STR_7", list7);
Map<String, List<String>> processedMap = proceedAndFetchRecords();
System.out.println("Final result");
for (Entry<String, List<String>> entry : processedMap.entrySet()) {
System.out.println("Key : " + entry.getKey() + " || Values : " + entry.getValue());
}
}
private static Map<String, List<String>> proceedAndFetchRecords() {
for (Entry<String, List<String>> entry : map.entrySet()) {
boolean flag = isNotDependent(entry.getValue());
// System.out.println("Key : " + entry.getKey() + " isNotDependent :
// " + flag);
if (flag) {
nonDependentList.add(entry.getKey());
} else {
List<String> list = getOnlyDependentList(entry.getValue());
dependentMap.put(entry.getKey(), list);
}
}
// showDependentMap();
refreshDependentMap();
// Final result
Map<String, List<String>> processedMap = addResultsInMap();
return processedMap;
}
private static void refreshDependentMap() {
for (Entry<String, List<String>> entry : dependentMap.entrySet()) {
List<String> list = entry.getValue();
list.removeAll(nonDependentList);
boolean flag = isNotDependent(list);
if (flag) {
nonDependentList.add(entry.getKey());
dependentMap.remove(entry.getKey());
refreshDependentMap();
} else {
continue;
}
}
}
private static Map<String, List<String>> addResultsInMap() {
Map<String, List<String>> processedMap = new LinkedHashMap<String, List<String>>();
for (String key : nonDependentList) {
processedMap.put(key, map.get(key));
}
for (Entry<String, List<String>> entry : dependentMap.entrySet()) {
processedMap.put(entry.getKey(), map.get(entry.getKey()));
}
return processedMap;
}
private static boolean isNotDependent(List<String> list) {
int count = 0;
for (String string : list) {
if (!string.startsWith("STR_")) {
++count;
}
}
if (list.size() == count) {
return true;
}
return false;
}
private static void addRecord(String structureName, List<String> list) {
map.put(structureName, list);
}
private static List<String> getOnlyDependentList(List<String> list) {
List<String> list1 = new ArrayList<>();
for (String string : list) {
if (string.startsWith("STR_")) {
list1.add(string);
}
}
return list1;
}}

Categories

Resources