Implementation of super MultiValue map - java

Is there any ready implementation of super MultiValue map?
I need a map
SuperMap<List<String>, List<String>> superMap
where key List is set of keys, and value List is set of values.
keys: {a, b, c} and values for any of these keys are values: {1, 2, 3, 4, 5}
It means that key a should have a values {1, 2, 3, 4, 5} as well as the key b and c.
Updated with requirements from comments
I need to get all keys of one group? For example I need to get collection of keys for one similar value. In that case I cannot use your approach map.put("a", value); because I need to group (a, b it is first group, and c is related to second group).
UPD2
I'm looking for a simple and much concise solution to replace this code
Map<List<String>, List<String>> ops = new HashMap<List<String>, List<String>>() {{
put(asList("a", "aaa"), asList("1", "2"));
put(asList("b", "bbb"), asList("3", "4"));
}};
public static List<String> values(String optionKey) {
for (List<String> key : ops.keySet()) {
for (String k : key) {
if (optionKey.equals(k)) {
return ops.get(key);
}
}
}
return Collections.emptyList();
}
with some smart impl form any well known lib (Google Guava or something).

I think you're overcomplicating your data structure. You can simply use HashMap or similar implementation.
Map<String, List<String>> map = new LinkedHashMap<>();
List<String> value = new ArrayList<>();
// Fill the value list here...
map.put("a", value);
map.put("b", value);
map.put("c", value);

Related

Convert Map<Integer, List<Strings> to Map<String, List<Integer>

I'm having a hard time converting a Map containing some integers as keys and a list of random strings as values.
E.g. :
1 = ["a", "b", "c"]
2 = ["a", "b", "z"]
3 = ["z"]
I want to transform it into a Map of distinct strings as keys and lists the integers as values.
E.g. :
a = [1, 2]
b = [1, 2]
c = [1]
z = [2,3]
Here's what I have so far:
Map<Integer, List<String>> integerListMap; // initial list is already populated
List<String> distinctStrings = new ArrayList<>();
SortedMap<String, List<Integer>> stringListSortedMap = new TreeMap<>();
for(Integer i: integers) {
integerListMap.put(i, strings);
distinctStrings.addAll(strings);
}
distinctStrings = distinctStrings.stream().distinct().collect(Collectors.toList());
for(String s : distinctStrings) {
distinctStrings.put(s, ???);
}
Iterate over your source map's value and put each value into the target map.
final Map<String, List<Integer>> target = new HashMap<>();
for (final Map.Entry<Integer, List<String>> entry = source.entrySet()) {
for (final String s : entry.getValue()) {
target.computeIfAbsent(s, k -> new ArrayList<>()).add(entry.getKey());
}
}
Or with the Stream API by abusing Map.Entry to build the inverse:
final Map<String, List<Integer>> target = source.entrySet()
.stream()
.flatMap(e -> e.getValue().stream().map(s -> Map.entry(s, e.getKey()))
.collect(Collectors.groupingBy(e::getKey, Collectors.mapping(e::getValue, Collectors.toList())));
Although this might not be as clear as introducing a new custom type to hold the inverted mapping.
Another alternative would be using a bidirectial map. Many libraries come implementations of these, such as Guava.
There's no need to apply distinct() since you're storing the data into the Map and keys are guaranteed to be unique.
You can flatten the entries of the source map, so that only one string (let's call it name) and a single integer (let's call it number) would correspond to a stream element, and then group the data by string.
To implement this problem using streams, we can utilize flatMap() operation to perform one-to-many transformation. And it's a good practice to define a custom type for that purpose as a Java 16 record, or a class (you can also use a Map.Entry, but note that approach of using a custom type is more advantages because it allows writing self-documenting code).
In order to collect the data into a TreeMap you can make use of the three-args version of groupingBy() which allows to specify mapFactory.
record NameNumber(String name, Integer number) {}
Map<Integer, List<String>> dataByProvider = Map.of(
1, List.of("a", "b", "c"),
2, List.of("a", "b", "z"),
3, List.of("z")
);
NavigableMap<String, List<Integer>> numbersByName = dataByProvider.entrySet().stream()
.flatMap(entry -> entry.getValue().stream()
.map(name -> new NameNumber(name, entry.getKey()))
)
.collect(Collectors.groupingBy(
NameNumber::name,
TreeMap::new,
Collectors.mapping(NameNumber::number, Collectors.toList())
));
numbersByName.forEach((name, numbers) -> System.out.println(name + " -> " + numbers));
Output:
a -> [2, 1]
b -> [2, 1]
c -> [1]
z -> [3, 2]
Sidenote: while using TreeMap it's more beneficial to use NavigableMap as an abstract type because it allows to access methods like higherKey(), lowerKey(), firstEntry(), lastEntry(), etc. which are declared in the SortedMap interface.

Compare HashMap values and return first key

I've got HashMap
Map<String, Integer> map = new HashMap<>();
map.put("b", 2);
map.put("a", 2);
map.put("c", 2);
I need to compare values, and if it equals, I need to return first key value "b", but map return "a".
How can I achieve it?
In HashMap, keys are not ordered, so you cannot tell which key was first inserted.
Have a look at LinkedHashMap for a Map with ordered keys.
Hashmaps are not designed to search for values (i.e, they are designed to search for keys). You may want to create a different hashmap for this:
Map<Integer, ArrayList<String>> map2 = new HashMap<>();
ArrayList<String> arr = new ArrayList<>();
arr.add("b");
arr.add("a");
arr.add("c");
map2.put(2, arr);
map2.get(2).get(0); // returns "b", i.e, first element added into the array

Adding to double Arraylist in HashMap

I am trying to add pairs that add up to a certain number in java and one of the ways I am trying to attempt this is to create a double ArrayList within my HashMap. If I add 1 and 2 to my list, I will get 3 as my key. For example:
HashMap<Integer, ArrayList<ArrayList<Integer>>> map = new HashMap<>();
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
map.put(list.get(0) + list.get(1), new ArrayList<>(list));
Output looks like this
Key: 3 Value: [[1,2]]
If I were to add one more pair
Key: 3 Value: [[1,2],[0,3]]
but I keep getting a 'method is not applicable in the type HashMap<Integer,ArrayList<ArrayList>> is not applicable for the arguments (int, new ArrayList<>(list))'
I've also tried
new ArrayList<>(new ArrayList<>(list))
thinking that I might need to initialize the bigger matrix first but I end up with the same error sadly.
This line:
new ArrayList<>(list)
creates a flat ArrayList<Integer>, while the HashMap is expecting ArrayList<ArrayList<Integer>>. By the same token, new ArrayList<>(new ArrayList<>(list)) also creates a flat Integer list because you are just doing the same thing twice. See the API document for ArrayList
This is one way that would work given the 2-D list setup:
HashMap<Integer, List<List<Integer>>> map = new HashMap<>();
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
List<List<Integer>> outer = new ArrayList<>();
outer.add(list);
map.put(list.get(0) + list.get(1), outer);
You could also create some lambdas which may facilitate this. For example.
Map<Integer, List<List<Integer>>> map1 = new HashMap<>();
Create a function to sum the elements of a list.
Function<List<Integer>, Integer> sum =
list -> list.stream()
.mapToInt(Integer::intValue).sum();
Then create a BiConsumer to take the list pair and existing map and add them if need be. The computeIfAbsent, enters a value for the key if the key was null or absent. The list is returned so the the pair can be added to the newly created list.
BiConsumer<List<Integer>, Map<Integer,
List<List<Integer>>>> addToMap =
(pair, map) -> {
map.computeIfAbsent(sum.apply(pair),
v -> new ArrayList<>()).add(pair);
};
Putting it all together.
addToMap.accept(List.of(1,2), map1);
addToMap.accept(List.of(0,4), map1);
addToMap.accept(List.of(1,5), map1);
addToMap.accept(List.of(0,3), map1);
addToMap.accept(List.of(-1,5), map1);
addToMap.accept(List.of(-1,2,3),map1);
map1.entrySet().forEach(System.out::println);
prints
3=[[1, 2], [0, 3]]
4=[[0, 4], [-1, 5], [-1, 2, 3]]
6=[[1, 5]]
As you can see, this doesn't enforce any size limitations on the "pairs."
This may be overkill for what you want but there may be some elements you can put to use. Also note that List.of above is immutable.

How can I group values of List in Java using Lambda like we do in python

I want to group values of a map based on the key. Let's say
Map<String,Integer> map1 = new TreeMap<String,Integer>();
map1.put("D", 3);
map1.put("B", 2);
map1.put("C", 1);
Map<String,Integer> map2 = new TreeMap<String,Integer>();
map2.put("A", 13);
map2.put("B", 22);
map2.put("C", 12);
Map<String,Integer> map3 = new TreeMap<String,Integer>();
map3.put("A", 33);
map3.put("B", 32);
map3.put("C", 32);
Map<Integer,Map<String,Integer>> map = new HashMap <Integer,Map<String,Integer>>();
map.put(1,map1);
map.put( 2, map2);
map.put(3, map3);
System.out.println(map);
I want to group values in the map based on the keys: Output should be ["A","B","C"]:[2,3], ["D","B","C"]:[1]
So what I have done:
Map<List<String>, List<Integer>> newMap = new HashMap<List<String>, List<Integer>>();
for (Integer item : map) {
Map<String,Integer> currentValue = map.get(item);
List<String> oldItemKeySet = newMap.get(currentValue.keySet());
newMap.put(currentValue.keySet(), (oldItemKeySet == null) ? 1 : oldItemKeySet.put());
}
But it doesn't work out, can anyone help here.
PS: In Python, these things can be done with itertools.groupby or reduce, but i am still don't knoww how to do it perfectly in Java
If I understand well, you want to group the identical key set of the maps you added in the last map associated with the original key.
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
...
Map<Set<String>, List<Integer>> newMap =
map.entrySet()
.stream()
.collect(groupingBy(e -> e.getValue().keySet(),
mapping(Map.Entry::getKey, toList())));
From the last map, you get the stream of entries (which is a Stream<Entry<Integer, Map<String, Integer>>). There you group the entries by the key set of their map's values.
Then you map the values of the resulting map using a downstream collector, which collects the keys of the original entries in a List<Integer>.
Output:
{[A, B, C]=[2, 3], [B, C, D]=[1]}

How to get values of some specific range of keys in HashMap in Java?

How to get values of some specific range of keys in HashMap in Java?
Suppose I have a HashMap having keys and values as follows:
Map <Integer , String> map = hnew HashMap <Integer , String>();
map.put(1 , "A");
map.put(3 , "C");
map.put(2 , "B");
map.put(4 , "D");
map.put(5 , "E");
map.put(6 , "F");
Now how should I iterate to get values for keys 2 to 5 (that is 2, 4, and 5 but not 3)?
Either:
Loop through the list of keys you want and query the HashMap for the value corresponding to that key (be aware some may be null).
Loop through the entrySet of the HashMap looking to see if the key is one that you want.
Use a map that keeps order (LinkedHashMap added in correct order) and iterate through, extracting the required block of entries.
While Java's Map interface does not define a special order on its elements, a map implementation may very well define one. The sub-interface SortedMap defines that all keys must be naturally ordered and adds new methods like subMap to access value ranges. The sub-sub-interface NavigableMap adds even more.
The most prominent implementation in Java's standard library of SortedMap is TreeMap:
SortedMap<Integer, String> map = new TreeMap<>();
map.put(1 , "A");
map.put(3 , "C");
map.put(2 , "B");
map.put(4 , "D");
map.put(5 , "E");
map.put(6 , "F");
System.out.println(map.subMap(2, 6).values()); // [B, C, D, E]
A Map has no order on the keys. It does not matter if you put the key 3 and then the key 2. This "order" is not preserved.
I don't see anything special about the keys 2, 4, and 5. So you could do this:
for (int key : new int[] {2, 4, 5}) {
String value = map.get(key);
}
If you want maintain the order of the insertion for map then use LinkedHashMap. Then you can iterate the map through the specific value .
Declare an array of Integer for which you would like the value.
int a[] = {2,4,5};
for(int i : a) {
String str = map.get(i);
doSomething(str); //method to operate on that string.
}
If you want to have a predictable order in the keys of a Map, you need to use subclass LinkedHashMap.
Here's an example of code that does what you want:
public class Snippet {
public static void main(String[] args) {
LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();
map.put(1, "A");
map.put(3, "C");
map.put(2, "B");
map.put(4, "D");
map.put(5, "E");
map.put(6, "F");
System.out.println(valuesBetween(2, 5, map));
}
public static List<String> valuesBetween(int from, int to, LinkedHashMap<Integer, String> map) {
List<String> result = new ArrayList<>();
boolean inRange = false;
for (Map.Entry<Integer, String> entry : map.entrySet()) {
if (entry.getKey() == from)
inRange = true;
if (inRange)
result.add(entry.getValue());
if (entry.getKey() == to)
break;
}
return result;
}
}
This will print:
[B, D, E]
if you want get int key values from HashMap
you should create new Set Array and put values to it.
**Set has specificity for sort it's keys**.
Map<Integer,String> students = new HashMap<>();
students.put(1, "Rashid");
students.put(3, "Cosqun");
students.put(2, "Ulvi");
Set<Integer> keys = students.keySet();
for (int key : keys) {
System.out.println(key+" "+students.get(key));
}
Dog, this is all you need:
SortedMap<Integer, String> map = new TreeMap<Integer, String>();
map.subMap(2, 6);

Categories