Using ComputeIfAbsent & ComputeIfPresent to put list into map - java

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);
}
}

Related

Hashmap within ArrayList in Java

There is a hash map within the ArrayList. the output is like below
[{A=2},{A=3},{B=1},{B=4},{A=3}]
Below I have mentioned my code sample
ArrayList<Map<String, Short>> deviceInfo = new ArrayList<>();
Map<String, Integer> rssiMapper = new HashMap<>();
rssiMapper.put(device.getName(), rssi);
deviceInfo.add(rssiMapper);
I want to take mean value of A and B separately. How can I achieve that
Try it like this.
List<Map<String,Integer>> list = List.of(
Map.of("A", 2),
Map.of("A", 3),
Map.of("B", 1),
Map.of("B", 4),
Map.of("A", 3));
Map<String, Double> avgs = list.stream()
.flatMap(m -> m.entrySet().stream())
.collect(Collectors.groupingBy(
Entry::getKey,
Collectors.averagingInt(Entry::getValue)));
System.out.println(avgs);
Prints
{A=2.6666666666666665, B=2.5}
As was suggested, if you are unfamiliar with streams, here is an iterative approach.
Map<String,Double> avgs = new HashMap<>();
Map<String,Integer> count = new HashMap<>();
for (Map<String,Integer> map : list) {
for (Entry<String,Integer> e : map.entrySet()) {
String key = e.getKey();
int value = e.getValue();
// These just either initialize or update the appropriate
// values.
avgs.compute(key, (k,v)-> v == null ? value : v + value);
count.compute(key, (k,v)->v == null ? 1 : v + 1);
}
}
// now find the averages.
for(Entry<String,Double> e : avgs.entrySet()) {
avgs.computeIfPresent(e.getKey(), (k,v)->v/count.get(e.getKey()));
}
System.out.println(avgs);
You can create maps to track the sum, count and the mean (i.e. sum / count) of the entries as shown below:
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class Main {
public static void main(String[] args) {
List<Map<String, Integer>> list = List.of(Map.of("A", 2), Map.of("A", 3), Map.of("B", 1), Map.of("B", 4),
Map.of("A", 3));
Map<String, Integer> sumMap = new HashMap<>();
Map<String, Integer> countMap = new HashMap<>();
Map<String, Double> meanMap = new HashMap<>();
for (Map<String, Integer> map : list) {
for (Entry<String, Integer> entry : map.entrySet()) {
sumMap.put(entry.getKey(), sumMap.getOrDefault(entry.getKey(), 0) + entry.getValue());
countMap.put(entry.getKey(), countMap.getOrDefault(entry.getKey(), 0) + 1);
meanMap.put(entry.getKey(),
(double) sumMap.getOrDefault(entry.getKey(), 0) / countMap.getOrDefault(entry.getKey(), 1));
}
}
// Display
System.out.println(meanMap);
}
}
Output:
{A=2.6666666666666665, B=2.5}

Seeking Optimization Suggestions for Map-to-Map Conversion

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

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.

Most frequent element stream

How to find most frequent element, but when there are few most frequent element return null.
I would like to find code equivalent of:
public static void main(String[] args) {
System.out.println("Should return A -> " + mostFrequent(Arrays.asList("A", "A", "B")));
System.out.println("Should null as element in list have same frequency -> "
+ mostFrequent(Arrays.asList("A", "B")));
}
private static String mostFrequent(List<String> elements) {
Map<String, Long> ordered = new TreeMap<>();
for (String e : elements) {
if (!ordered.containsKey(e)) {
ordered.put(e, 0L);
}
Long tmp = ordered.get(e);
ordered.put(e, ++tmp);
}
String mostFrequent = null;
long i = 0;
Iterator<Map.Entry<String, Long>> it = ordered.entrySet().iterator();
while (it.hasNext() && i < 2) {
Map.Entry<String, Long> pair = it.next();
if (i == 0) {
mostFrequent = pair.getKey();
} else {
if (ordered.get(mostFrequent) == ordered.get(pair.getKey())) {
return null;
}
}
i++;
}
return mostFrequent;
}
However stream version does not handle most frequent elements with the same frequency.
private static String mostFrequentStream(List<String> elements) {
return elements.stream()
.reduce(BinaryOperator.maxBy(
Comparator.comparingInt(o -> Collections.frequency(elements, o))))
.orElse(null);
}
How to modify stream above to achieve it?
using groupingBy:
String mostFrequentStream(List<String> elements) {
Map<String, Long> temp = elements.stream()
.collect(Collectors.groupingBy(a -> a, Collectors.counting()));
return new HashSet<>(temp.values()).size() < temp.size() ?
null : temp.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey).get();
}
I managed to build a concatenated Stream but it got long:
private static String mostFrequentStream3(List<String> elements) {
return elements.stream() // part 1
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream() // part 2
.collect(Collectors.groupingBy(Entry::getValue))
.entrySet().stream() // part 3
.max(Entry.comparingByKey())
.map(Entry::getValue)
.filter(v -> v.size() == 1)
.map(v -> v.get(0).getKey())
.orElse(null);
}
To "find most frequent element, but when there are few most frequent element return null"
Part 1 counts the frequency of every element.
Part 2 groups entries by frequency.
Part 3 looks up the entry with the highest frequency. If this entry does only have one element ("there are few most frequent"), then it's the one and only maximum. Otherwise null is returned.
I would never use stream for this to avoid hurting readability and performance at the same time. For the sake of fun -
private static String mostFrequentStream(List<String> elements) {
Map<String, Long> frequencyMap = elements.stream().collect(groupingBy(Function.identity(), counting()));
return frequencyMap.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(2).reduce((i, e) -> i.getValue().equals(e.getValue()) ? new AbstractMap.SimpleEntry<>(null, 0L) : i).get().getKey();
}

Need a Fresh pair of Eyes to Work Out the Logic Behind Comparing Values in a Map of Maps

Problem
Data is in the format:
Map<String, Map<String, Integer>>
Which looks something like this:
{"MilkyWay": {"FirstStar" : 3, "SecondStar" : 9 .... }, "Andromeda": {"FirstStar" : 10, "SecondStar" : 9 .... } }
I want to compare the Star values in a quick loop, so I'd like to compare the integer value of FirstStar in MilkyWay and Andromeda and have it return true or falseif the values are the same or not. Since this Map of Maps is huge.
My Attempt
I'd like to do it something like:
//GalaxyMap<String, <Map<String, Integer>>
for (Map<String, Integer> _starMap : GalaxyMap.values())
{
for (String _starKey : _starMap.keySet()){
//Can't quite think of the correct logic... and I'm tired...
}
}
I'd like to keep it as short as possible... I've been staring at this for a while and I'm going in circles with it.
EDIT
Outer keys differ, Inner keys are the same
Also since this is a response from a server, I don't know the size it's going to be
Why does this need to be a map. If you're always using "FirstStar", "SecondStar" etc, as your keys, then why not make it a list instead..
Map<String, List<Integer>>
Then you can do something like:
public boolean compareGalaxyStar(String galaxyName, String otherGalaxyName, int star) {
List<Integer> galaxyStars = galaxyMap.get(galaxyName);
List<Integer> otherGalaxyStars = galaxyMap.get(otherGalaxyName);
return galaxyStars.get(star) == otherGalaxyStars.get(star);
}
NOTE: You need to do some validation to make sure the input is correct.
To implement this for all stars, it is not much different.
if(galaxyStars.size() == otherGalaxyStars.size()) {
for(int x = 0; x < galaxyStars.size(); x++) {
// Perform your comparisons.
if(galaxyStars.get(x) != otherGalaxyStars.get(x)) {
// Uh oh, unequal.
return false;
}
}
}
If the structure of the inner maps also could differ, you should do something like that:
static boolean allStarValuesEqual(Map<String, Map<String, Integer>> galaxies) {
Map<String, Integer> refStars = null;
for (Map<String, Integer> galaxy : galaxies.values()) {
if (refStars == null) {
refStars = galaxy;
} else {
for (Entry<String, Integer> currentStar : galaxy.entrySet()) {
if (!currentStar.getValue().equals(refStars.get(currentStar.getKey()))) {
return false;
}
}
}
}
return true;
}
Please check below program along with output:
package com.test;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class CompareMapValues {
private final static String FS = "FirstStar";
private final static String SS = "SecondStar";
private final static String MW = "MilkyWay";
private final static String A = "Andromeda";
public static void main(String[] args) {
Map> map = new HashMap>();
Map innerMap1 = new HashMap();
innerMap1.put(FS, 3);
innerMap1.put(SS, 9);
Map innerMap2 = new HashMap();
innerMap2.put(FS, 10);
innerMap2.put(SS, 9);
map.put(MW, innerMap1);
map.put(A, innerMap2);
Set set = map.keySet();
for(String s: set) {
Map outerMap = map.get(s);
Set set2 = map.keySet();
for(String s2: set2) {
Map innerMap = map.get(s2);
if(!s2.equals(s)) {
Set set3 = outerMap.keySet();
for(String s3: set3) {
int i1 = outerMap.get(s3);
Set set4 = innerMap.keySet();
for(String s4: set4) {
int i2 = innerMap.get(s3);
if(s3.equals(s4) && i1==i2) {
System.out.println("For parent " + s + " for " + s3 + " value is " + i1);
}
}
}
}
}
}
}
}
//Output:
//For parent Andromeda for SecondStar value is 9
//For parent MilkyWay for SecondStar value is 9
Hope this helps.

Categories

Resources