I'm putting some arrays into hashmap with 2 keys. Then I'm trying to extract these values, but can get values only for last keys. For any another keys I'm getting null pointer exception:
"Exception in thread "main" java.lang.RuntimeException: No xyValues
for the keys: 'L', '18.2' at
tmp.DataScan1.getSerie(DataScan1.java:49) at
tmp.DataScan1.main(DataScan1.java:66)"
What's wrong?
Here is my working example:
import java.util.Arrays;
import java.util.HashMap;
public class DataScan1 extends HashMap<Character, DataSerie>{
public static double[] freqs;
public void putSerie(char lriv, double freq, double[][] xyValues){
char key1 = lriv;
long key2 = double2key(round(freq, 4));
DataSerie dataSerie = new DataSerie();
dataSerie.put(key2, xyValues);
this.put(key1, dataSerie);
}
private static long double2key(double value){
long result = (long) (value * 10000);
return result;
}
public DataScan1(){
freqs = new double[]{1, 16.9,4.0,18.2,17.4};
for (int idxfreq=0; idxfreq<freqs.length; idxfreq++){
double[][] array = new double[][]{{1, 2}, {3,4}};
putSerie('L', freqs[idxfreq], array);
}
}
public double[][] getSerie(char lriv, double freq){
char key1 = lriv;
long key2 = double2key(round(freq, 4));
double[][] xyValues = this.get(key1).get(key2);
if (xyValues == null){
throw new RuntimeException("No xyValues for the keys: '" + lriv + "', '" + freq + "'");
}
return xyValues;
}
public static void printArr(double[] arr){
System.out.println(Arrays.toString(arr));
}
public static double round(double d, int decimalPlace) {
BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
}
public static void main(String[] args) {
DataScan1 myData = new DataScan1();
printArr(myData.freqs);
double[][] qq = myData.getSerie('L', 17.4); // that serie exist
double[][] qqq = myData.getSerie('L', 18.2); // but this -- isn't, error here
}
}
UPDATE:
I forgot DataSerie definition:
public class DataSerie extends HashMap<Long, double[][]>{
}
Simply spoken: bad idea.
You want to use a floating point value as key for a Map. That means that Double objects will be created; and compared using their equals() methods.
And the thing is: you don't do that. When you compare two floating point numbers, you always always always do something along the lines of (x1 - x2) < epsilon. (see here for more examples why that is a bad idea; and then here for some alternatives; and finally here for some explanations; esp. answer no. 2 by Bernd)
In other words: if you really want to use those numbers as key, then keep them represented as strings!
And beyond that: avoid mixing concepts - you decided to use Maps; so don't make things more complicated by using arrays, too. I would rather define a "Matrix" class to hold the content that is currently in your double[][] array.
Finally, for your real problem with the current code:
this.put(key1, dataSerie);
Here you putting the new dataSerie object into your map. If you look closely, you will find that you keep using the same key (the char 'L') all the time. In other words: yes, your code creates a new dataSerie object; but then you are overwriting the one and only entry in your map with that value.
Thus, there are two ways to go:
In case that this map key is always the same character; well, then you do not need to use a map here. Then you would be using a List and just add your values!
If your program actually has to deal with different keys, then you might better use a Map<Char, List<double[][]>> for example.
But honestly; I think the real take away here is: step back, and work your way through this in order to really understand the things you intend to use!
In putSerie() you always create a new DataSerie and replace the old one at key 'L'
Instead, you should only create a new DataSerie there if none exists for key 'L'. If it does already exist, just take the existing one and insert your new element (xyValues) into it.
public void putSerie(char lriv, double freq, double[][] xyValues){
char key1 = lriv;
long key2 = double2key(freq);
DataSerie dataSerie;
if (!this.containsKey(key1)) {
// Only create a new one, when necessary
dataSerie = new DataSerie();
this.put(key1, dataSerie);
} else {
dataSerie = this.get(key1);
}
dataSerie.put(key2, xyValues);
}
Related
I recently asked about converting Json using Gson into something I can sort values into, and the best option was using a Linked HashMap.
List<String> stringList = Arrays.asList(tm.split(" |,")); // split into pair key : value
Map<String, List<String>> mapString = new LinkedHashMap<>();
stringList.forEach(s1 -> {
String[] splitedStrings = s1.split(": "); //split into key : value
String key = splitedStrings[0].replaceAll("[^A-Za-z0-9]",""); // remove non alphanumeric from key, like {
String value = splitedStrings[1];
if (mapString.get(key) == null) {
List<String> values = new ArrayList<>();
values.add(value);
mapString.put(key, values);
}else if (mapString.get(key) != null) {
mapString.get(key).add(value);
}
});
When this code is run, a map with keys for frequency, magnitude, and other attributes of my data is created. This is the original Json Message compared to the resulting map value for the same set of data (Formatted to make it easier to understand and look better)
{"groupId":"id3_x_","timestamp":1.591712740507E9,"tones":
[{"frequency":1.074,"level":3.455,"bw":0.34,"snr":3.94,"median":0.877},
{"frequency":14.453,"level":2.656,"bw":0.391,"snr":2.324,"median":1.143},
{"frequency":24.902,"level":0.269,"bw":0.282,"snr":2.216,"median":0.121},
{"frequency":22.607,"level":0.375,"bw":0.424,"snr":2.034,"median":0.184},
{"frequency":9.863,"level":2.642,"bw":0.423,"snr":1.92,"median":1.376}]}
To Map values:
Message Received
Group ID: id3_x_
Message Topic: pi7/digest/tonals
Time of Arrival: 1.591712740507E9
---------------DATA---------------
Frequency: [1.07, 14.45, 24.90, 22.61, 9.86]
Magnitude: [3.46, 2.66, 0.27, 0.38, 2.64]
Bandwidth: [0.34, 0.39, 0.28, 0.42, 0.42]
SNR: [3.94, 2.32, 2.22, 2.03, 1.92]
Median: [0.88, 1.14, 0.12, 0.18, 1.38]]
While this is very useful for analyzing the data, the information stored is a string. What I would like to be able to do is transform each of the values in the map (Example: Frequency 1.07, 14.45, etc.) into doubles that i can then run through additional programs and run calculations with, such as an average. I have looked around online and havnt found anything that I am looking for, so im wondering if there would be a way to transform these strings into doubles using either an array, list, or any other means.
I am an intern for a tech company so I am still trying to hammer in Java and describing what I am talking about, so if there is any questions about what I am asking, please let me know and thanks in advance!
You could get a Map from the JSON file , you can also extract the values array from the Map yourmap.getvalues() , then you can parse each on of these element and case it into double
Example : Frequency: [1.07, 14.45, 24.90, 22.61, 9.86]
for ( String f : Frequency ) {
double f_double = Double.parse(f); // turns String into double
}
You can do this with another class that will store duplicate attribute values in arrays. You can simply get them through a.getValues (). This is just a concept and you should extend it as it will be convenient for you.
import java.util.*;
public class Main {
public static void main(String[] args) {
Map<String, List<Attribute>> map = new LinkedHashMap<>();
List<Attribute> attributes = new ArrayList<>();
attributes.add(new Attribute("frequency", 3.46, 5.11, 6.12));
attributes.add(new Attribute("magnitude", 3.46, 10.22, 10.54));
//and so on
map.put("idString1", attributes);
//printing double values
for (String key : map.keySet()) {
for (Attribute a : map.get(key)) {
System.out.println(a.getName() + " " +Arrays.toString(a.getValues()));
//a.getValues() to get all of doubles
}
}
}
private static class Attribute {
private String name;
private double[] values;
Attribute(String name, double... values) {
this.name = name;
this.values = values;
}
String getName() {
return name;
}
double[] getValues() {
return values;
}
}
}
The result will be:
frequency [3.46, 5.11, 6.12]
magnitude [3.46, 10.22, 10.54]
Your question:
I would like to be able to do is transform each of the String values in the
map (Example: Frequency 1.07, 14.45, etc.) into doubles and run calculations with, such as an average.
Yes, it is possible to transform your String array in a double array using Stream like below:
String[] frequencies = { "1.07", "14.45", "24.90", "22.61", "9.86" };
double[] arr = Stream.of(frequencies)
.mapToDouble(Double::parseDouble)
.toArray();
If you use the DoubleSummaryStatistics class you have already available ops like avg, sum :
String[] frequencies = { "1.07", "14.45", "24.90", "22.61", "9.86" };
DoubleSummaryStatistics statistics = Stream.of(frequencies)
.mapToDouble(Double::parseDouble)
.summaryStatistics();
System.out.println(statistics.getAverage()); //<-- 14.578
System.out.println(statistics.getMax()); //<-- 24.9
I am having a hard time understanding the right syntax to sort Maps which values aren't simply one type, but can be nested again.
I'll try to come up with a fitting example here:
Let's make a random class for that first:
class NestedFoo{
int valA;
int valB;
String textA;
public NestedFoo(int a, int b, String t){
this.valA = a;
this.valB = b;
this.textA = t;
}
}
Alright, that is our class.
Here comes the list:
HashMap<Integer, ArrayList<NestedFoo>> sortmePlz = new HashMap<>();
Let's create 3 entries to start with, that should show sorting works already.
ArrayList<NestedFoo> l1 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,2,"a");
n3 = new NestedFoo(1,4,"c");
l1.add(n1);
l1.add(n2);
l1.add(n3);
ArrayList<NestedFoo> l2 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,2,"a");
n3 = new NestedFoo(2,2,"b");
n4 = new NestedFoo(1,4,"c");
l2.add(n1);
l2.add(n2);
l2.add(n3);
l2.add(n4);
ArrayList<NestedFoo> l3 = new ArrayList<>();
n1 = new NestedFoo(3,2,"a");
n2 = new NestedFoo(2,3,"b");
n3 = new NestedFoo(2,2,"b");
n4 = new NestedFoo(5,4,"c");
l3.add(n1);
l3.add(n2);
l3.add(n3);
l3.add(n4);
Sweet, now put them in our Map.
sortmePlz.put(5,l1);
sortmePlz.put(2,l2);
sortmePlz.put(1,l3);
What I want now, is to sort the Entire Map first by its Keys, so the order should be l3 l2 l1.
Then, I want the lists inside each key to be sorted by the following Order:
intA,intB,text (all ascending)
I have no idea how to do this. Especially not since Java 8 with all those lambdas, I tried to read on the subject but feel overwhelmed by the code there.
Thanks in advance!
I hope the code has no syntatical errors, I made it up on the go
You can use TreeSet instead of regular HashMap and your values will be automatically sorted by key:
Map<Integer, ArrayList<NestedFoo>> sortmePlz = new TreeMap<>();
Second step I'm a little confused.
to be sorted by the following Order: intA,intB,text (all ascending)
I suppose you want to sort the list by comparing first the intA values, then if they are equal compare by intB and so on. If I understand you correctly you can use Comparator with comparing and thenComparing.
sortmePlz.values().forEach(list -> list
.sort(Comparator.comparing(NestedFoo::getValA)
.thenComparing(NestedFoo::getValB)
.thenComparing(NestedFoo::getTextA)));
I'm sure there are way of doing it with lambda but it is not actually required. See answer from Schidu Luca for a lambda like solution.
Keep reading if you want an 'old school solution'.
You cannot sort a map. It does not make sense because there is no notion of order in a map. Now, there are some map objects that store the key in a sorted way (like the TreeMap).
You can order a list. In your case, makes the class NestedFoo comparable (https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html). Then you can invoke the method Collections.sort (https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#sort-java.util.List-) on your lists.
Use TreeMap instead of HashMap, it solves the 1st problem: ordering entries by key.
After getting the needed list from the Map, you can sort the ArrayList by valA, valB, text:
l1.sort(
Comparator.comparing(NestedFoo::getValA).thenComparing(NestedFoo::getValB).thenComparing(NestedFoo::getTextA)
);
And change your NestedFoo class definition like this:
class NestedFoo {
int valA;
int valB;
String textA;
public NestedFoo(int a, int b, String t) {
this.valA = a;
this.valB = b;
this.textA = t;
}
public int getValA() {
return valA;
}
public void setValA(int valA) {
this.valA = valA;
}
public int getValB() {
return valB;
}
public void setValB(int valB) {
this.valB = valB;
}
public String getTextA() {
return textA;
}
public void setTextA(String textA) {
this.textA = textA;
}
}
When using treemap for sorting keep in mind that treemap uses compareTo instead of equals for sorting and to find duplicity. compareTo should be incosistent with equals and hashcode when implemented for any object which will be used as key. You can look for a detailed example on this link https://codingninjaonline.com/2017/09/29/unexpected-results-for-treemap-with-inconsistent-compareto-and-equals/
I have an ArrayList as defined below:
List<String> where = new ArrayList<String>();
where.add("1 Kg");
where.add("500 gram");
where.add("5 Kg");
When I display this list, values shown are shown as:
1 Kg
500 gram
5 Kg
I want it to be displayed as given below:
500 gram
1 Kg
5 Kg
How should I sort it.
You need a Comparator where you can write your comparison logic.
See the following implementation.
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> where = new ArrayList<String>();
where.add("5 Kg");
where.add("500 gram");
where.add("1 Kg");
Collections.sort(where, new MassComparator());
for(String mass : where) {
System.out.println(mass);
}
}
public static class MassComparator implements Comparator<String> {
#Override
public int compare(String weight1, String weight2) {
double val1 = Double.parseDouble(weight1.replace("gram", "").replace("Kg", "").trim());
double val2 = Double.parseDouble(weight2.replace("gram", "").replace("Kg", "").trim());
if (weight1.contains("gram")) {
val1 *= .001;
}
if (weight2.contains("gram")) {
val2 *= .001;
}
int result = 0;
if (val1 < val2) {
result = -1;
} else if (val1 > val2) {
result = 1;
}
return result;
}
}
}
Input
List<String> where = new ArrayList<String>();
where.add("500 gram");
where.add("2 Kg");
Output
500 gram
2 Kg
If you really want to sort String representations of values instead of harmonizing the values in your data (e.g. all in grams) and sort them in the natural order of the values only, you'll need quite some work.
Use a Comparator<String> - see here for API. You'll inject this in an invocation of Collections.sort when sorting your List (API here).
In your compare method, parse two things out of each String through regular expressions: a number (real or integer, depends on your data) and a measurement unit (possibly based on an map defining all variants for gram, kilogram, etc.)
Then compare the measurement units (possibly using another Comparator<String>!) and convert the parsed numbers to their values for a single unit (likely grams, etc.)
Finally compare the harmonized numerical values after converting them to actual numbers (e.g. Integers or Doubles, etc.) - using their natural order this time
"Optionally", handle all edge cases: null or empty values, Strings not containing a numerical representation or measurement unit, ambiguous values, etc. etc.
Store your values as Double as shown below
List<Double> where = new ArrayList<Double>();
where.add(0.5);
where.add(1);
where.add(5);
You can apply sorting on above list,
also at the time of retrieving you can use 0.5 kg instead of 500 grams if your app requirement allows it.
Mr #11thdimension has already given a perfect working solution. I think you can also use Java 8, if you want. Basically the same idea but less code.
List<String> where = new ArrayList<String>();
where.add("1 Kg");
where.add("500 gram");
where.add("5 Kg");
Collections.sort(where, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
double a = (Double.parseDouble(o1.replace("gram","").replace("Kg","").trim()))*(o1.contains("gram")?0.001:1);
double b = (Double.parseDouble(o2.replace("gram","").replace("Kg","").trim()))*(o2.contains("gram")?0.001:1);
return Double.compare(a,b) ;
}
});
where.stream().forEach(System.out::println);
just use index and for cycle,
for(int i=0; i<where.size(); i++){
System.out.println(where.get(i));
}
My problem is can't get an object "Item" (value) from my Treemap. I need send that info to my GUI class and display it in JList to get a select list, so can easily select and add songs to playlist, but only what I get as an output is "01, 02, 03, 04, 05" (key). Please help, because I'm beginner and have no idea what to do.
public class LibraryData {
private static class Item {
Item(String n, String a, int r) {
name = n;
artist = a;
rating = r;
}
// instance variables
private String name;
private String artist;
private int rating;
private int playCount;
public String toString() {
return name + " - " + artist;
}
}
private static Map<String, Item> library = new TreeMap<String, Item>();
static {
library.put("01", new Item("How much is that doggy in the window", "Zee-J", 3));
library.put("02", new Item("Exotic", "Maradonna", 5));
library.put("03", new Item("I'm dreaming of a white Christmas", "Ludwig van Beethoven", 2));
library.put("04", new Item("Pastoral Symphony", "Cayley Minnow", 1));
library.put("05", new Item("Anarchy in the UK", "The Kings Singers", 0));
}
public static String[] getLibrary() {
String [] tempa = (String[]) library.keySet().toArray(new String[library.size()]);
return tempa;
}
SOLUTION:
Because I've to pass the values to another class:
JList tracks = new JList(LibraryData.getLibrary());
I made something like that and it's works
public static Object[] getLibrary() {
Collection c = library.values();
return c.toArray(new Item[0]);
Thank You guys, after 10 hours I finally done it!
}
With this code that you have:
String [] tempa = (String[]) library.keySet().toArray(new String[library.size()]);
You are getting all keys from the map. If you want all values, then use:
library.values();
Finally, if you need to get a value by key use V get(Object key):
library.get("01");
Which will return you the first Item from the map.
It's not very clear which one of these you want, but basically these are the options.
** EDIT **
Since you want all values you can do this:
library.values().toArray()
JList expects an array or vector of Object so this should work.
If you want to get value and key by position, you can use:
key: library.keySet().toArray()[0]
value: library.get(key);
OR (if you just want value)
library.values().toArray()[0];
You can use the ArrayList:
1 - The best for flexible-array managing in Java is using ArrayLists
2 - ArrayLists are easy to add, get, remove and more from and to.
3 - Treemaps are a little... arbitrary. What I say is that if you use the get(Object o) method from a Treemap, the Object o must be a key, which is something not very flexible.
If you want them, use this code:
import java.util.ArrayList;
import com.example.Something; // It can be ANYTHING
//...
ArrayList<Something> somethingList = new ArrayList<Something>();
//...
somethingList.add(new Something("string", 1, 2.5, true));
//...
boolean isSomething = somethingList.get(somethingList.size() - 1); // Gets last item added
//...
int listSize = somethingList.size();
//...
somethingList.remove(somethingList.size() - 1); // Removes last item and decrements size
//...
Something[] nativeArray = somethingList.toArray(new Something[somethingList.size()]); // The parameter is needed or everthing will point to null
// Other things...
Or the classic Treemap:
Object keyAtIndex0 = library.keySet.toArray(new Object[library.size()])[0];
Object value = library.get(keyAtIndex0);
Good Luck!
I was returning a list of string values as treemap value. The used approach is
private Map<String, TreeSet<String>> result;
TreeSet<String> names= result.get(key);
for(String contactName: names){
print contactName;
}
I would like to store a group of objects in a hashmap , where the key shall be a composite of two string values. is there a way to achieve this?
i can simply concatenate the two strings , but im sure there is a better way to do this.
You could have a custom object containing the two strings:
class StringKey {
private String str1;
private String str2;
}
Problem is, you need to determine the equality test and the hash code for two such objects.
Equality could be the match on both strings and the hashcode could be the hashcode of the concatenated members (this is debatable):
class StringKey {
private String str1;
private String str2;
#Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof StringKey) {
StringKey s = (StringKey)obj;
return str1.equals(s.str1) && str2.equals(s.str2);
}
return false;
}
#Override
public int hashCode() {
return (str1 + str2).hashCode();
}
}
You don't need to reinvent the wheel. Simply use the Guava's HashBasedTable<R,C,V> implementation of Table<R,C,V> interface, for your need. Here is an example
Table<String, String, Integer> table = HashBasedTable.create();
table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);
System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100
table.put("key-1", "lock-1", 150); //replaces 50 with 150
public int hashCode() {
return (str1 + str2).hashCode();
}
This seems to be a terrible way to generate the hashCode: Creating a new string instance every time the hash code is computed is terrible! (Even generating the string instance once and caching the result is poor practice.)
There are a lot of suggestions here:
How do I calculate a good hash code for a list of strings?
public int hashCode() {
final int prime = 31;
int result = 1;
for ( String s : strings ) {
result = result * prime + s.hashCode();
}
return result;
}
For a pair of strings, that becomes:
return string1.hashCode() * 31 + string2.hashCode();
That is a very basic implementation. Lots of advice through the link to suggest better tuned strategies.
Why not create a (say) Pair object, which contains the two strings as members, and then use this as the key ?
e.g.
public class Pair {
private final String str1;
private final String str2;
// this object should be immutable to reliably perform subsequent lookups
}
Don't forget about equals() and hashCode(). See this blog entry for more on HashMaps and keys, including a background on the immutability requirements. If your key isn't immutable, then you can change its components and a subsequent lookup will fail to locate it (this is why immutable objects such as String are good candidates for a key)
You're right that concatenation isn't ideal. For some circumstances it'll work, but it's often an unreliable and fragile solution (e.g. is AB/C a different key from A/BC ?).
I have a similar case. All I do is concatenate the two strings separated by a tilde ( ~ ).
So when the client calls the service function to get the object from the map, it looks like this:
MyObject getMyObject(String key1, String key2) {
String cacheKey = key1 + "~" + key2;
return map.get(cachekey);
}
It is simple, but it works.
I see that many people use nested maps. That is, to map Key1 -> Key2 -> Value (I use the computer science/ aka haskell curring notation for (Key1 x Key2) -> Value mapping which has two arguments and produces a value), you first supply the first key -- this returns you a (partial) map Key2 -> Value, which you unfold in the next step.
For instance,
Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance
add(k1, k2, value) {
table2 = table1.get(k1);
if (table2 == null) table2 = table1.add(k1, new HashMap())
table2.add(k2, value)
}
get(k1, k2) {
table2 = table1.get(k1);
return table2.get(k2)
}
I am not sure that it is better or not than the plain composite key construction. You may comment on that.
Reading about the spaguetti/cactus stack I came up with a variant which may serve for this purpose, including the possibility of mapping your keys in any order so that map.lookup("a","b") and map.lookup("b","a") returns the same element. It also works with any number of keys not just two.
I use it as a stack for experimenting with dataflow programming but here is a quick and dirty version which works as a multi key map (it should be improved: Sets instead of arrays should be used to avoid looking up duplicated ocurrences of a key)
public class MultiKeyMap <K,E> {
class Mapping {
E element;
int numKeys;
public Mapping(E element,int numKeys){
this.element = element;
this.numKeys = numKeys;
}
}
class KeySlot{
Mapping parent;
public KeySlot(Mapping mapping) {
parent = mapping;
}
}
class KeySlotList extends LinkedList<KeySlot>{}
class MultiMap extends HashMap<K,KeySlotList>{}
class MappingTrackMap extends HashMap<Mapping,Integer>{}
MultiMap map = new MultiMap();
public void put(E element, K ...keys){
Mapping mapping = new Mapping(element,keys.length);
for(int i=0;i<keys.length;i++){
KeySlot k = new KeySlot(mapping);
KeySlotList l = map.get(keys[i]);
if(l==null){
l = new KeySlotList();
map.put(keys[i], l);
}
l.add(k);
}
}
public E lookup(K ...keys){
MappingTrackMap tmp = new MappingTrackMap();
for(K key:keys){
KeySlotList l = map.get(key);
if(l==null)return null;
for(KeySlot keySlot:l){
Mapping parent = keySlot.parent;
Integer count = tmp.get(parent);
if(parent.numKeys!=keys.length)continue;
if(count == null){
count = parent.numKeys-1;
}else{
count--;
}
if(count == 0){
return parent.element;
}else{
tmp.put(parent, count);
}
}
}
return null;
}
public static void main(String[] args) {
MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
m.put("brazil", "yellow", "green");
m.put("canada", "red", "white");
m.put("USA", "red" ,"white" ,"blue");
m.put("argentina", "white","blue");
System.out.println(m.lookup("red","white")); // canada
System.out.println(m.lookup("white","red")); // canada
System.out.println(m.lookup("white","red","blue")); // USA
}
}
public static String fakeMapKey(final String... arrayKey) {
String[] keys = arrayKey;
if (keys == null || keys.length == 0)
return null;
if (keys.length == 1)
return keys[0];
String key = "";
for (int i = 0; i < keys.length; i++)
key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");
keys = Arrays.copyOf(keys, keys.length + 1);
keys[keys.length - 1] = FAKE_KEY_SEPARATOR;
return MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";
INPUT:
fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3
I’d like to mention two options that I don’t think were covered in the other answers. Whether they are good for your purpose you will have to decide yourself.
Map<String, Map<String, YourObject>>
You may use a map of maps, using string 1 as key in the outer map and string 2 as key in each inner map.
I do not think it’s a very nice solution syntax-wise, but it’s simple and I have seen it used in some places. It’s also supposed to be efficient in time and memory, while this shouldn’t be the main reason in 99 % of cases. What I don’t like about it is that we’ve lost the explicit information about the type of the key: it’s only inferred from the code that the effective key is two strings, it’s not clear to read.
Map<YourObject, YourObject>
This is for a special case. I have had this situation more than once, so it’s not more special than that. If your objects contain the two strings used as key and it makes sense to define object equality based on the two, then define equals and hashCode in accordance and use the object as both key and value.
One would have wished to use a Set rather than a Map in this case, but a Java HashSet doesn’t provide any method to retrieve an object form a set based on an equal object. So we do need the map.
One liability is that you need to create a new object in order to do lookup. This goes for the solutions in many of the other answers too.
Link
Jerónimo López: Composite key in HashMaps on the efficiency of the map of maps.