Efficient way to get all map values whose keys match a criterion - java

I have following map Map<IndexDerivedKey, Collection<Data<D>>> indexedData in an indexable data structure.
IndexDerivedKey conforms to an Index, an index consists of keys and corresponding values. The keys are used to extract the values from the elements in the indexable data structure.
For example there's an index, which consists of the keys firstName and lastName, we use this index to extract values from objects, which possess these attributes, this operation yields an IndexDerivedKey per object.
That IndexDerivedKey contains the mapping from above keys to their respective values and is used to store the objects in the map indexedData, which I mentioned before. The map value is a collection type, since it is possible for several objects to be equal (with respect to the index).
QUESTION
How can I extract all objects with firstName = "John", hereby ignoring the value of lastName. Obviously I can iterate over all keys and check the value of firstName in O(n).
But since the IndexDerivedKey {firstName = "John"} is a subset of all other keys with firstName = "John", e.g. {firstName = "John", lastName = "Smith"}, I suppose there has to be a more efficient way. Maybe utilizing a TreeSet?
1
public IndexDerivedKeyImpl(Index index, Map<String, String> keyValues)
{
this.keyValues = keyValues;
this.index = index;
for (String key : keyValues.keySet())
if (!index.supportsKey(key))
throw new IndexKeyMismatchException(key, index);
}
2
// in data.Index.index(Indexable)
#Override
public IndexDerivedKey index(Indexable data)
{
Map<String, String> keyValues = new HashMap<String, String>();
IndexDerivedKey key = new IndexDerivedKeyImpl(this, keyValues);
for (String k : keys)
{
String value = data.get(k);
if (value != null)
keyValues.put(k, value);
}
return key;
}

If the search is always on a specific order, make a key class with a Comparable, and equals.
And then use a SortedMap, like TreeMap. This is navigable too. You might for instance use subMap(fromKey, toKey)

If you want to access your Map with an other key, you are better off using an other Map:
Map<String, Collection<Data<D>>> dataByFirstName = // ...
Where the keys are the first names.

Related

Compare value according to key in LinkedHashMap to another LinkedHashMap using java

I have two linked hashmap (key - String, value = String[]) which got the same size and the same keys in both linked hashmaps, I want to be able to compare values according to the key, verifying values on one linked hashmap are equals to the same values in the second linked hashmap (by key) or at least the other linked hashmap contains the values.
I am populating both of the linked hashmaps with keys and values and set it to different linked hash maps.
Example for hashmap:
Key - alert - Value (array of strings)
0 - Device_UID,Instance_UID,Configuration_Set_ID,Alert_UID
1 - a4daeccb-0115-430c-b516-ab7edf314d35,0a7938aa-9a01-437f-88ac-4b2927ed7665,96,61b68069-9de7-4b85-83cb-8d9f558e8ecb
2 - a4daeccb-0115-430c-b516-ab7edf314d35,0a7938aa-9a01-437f-88ac-4b2927ed7665,12,92757faa-bf6b-4aa3-ba6d-2e57b44f333c
3 - a4daeccb-0115-430c-b516-ab7edf314d35,0a7938aa-9a01-437f-88ac-4b2927ed7665,369,779b3294-2ca3-4613-a413-bf8d4aa05d16
and it should be at least in the second linked hash- map
String rdsColumns="";
for(String key : mapServer.keySet()){
String[] value = mapServer.get(key);
String[] item = value[0].split(",");
rdsColumns="";
for(String val:item){
rdsColumns = rdsColumns.concat(val + ",");
}
rdsColumns = rdsColumns.concat(" ");
rdsColumns = rdsColumns.replace(", ", "");
info(("Query is: "+ returnSuitableQueryString(rdsColumns, key, alertId, deviceId)));
String query=returnSuitableQueryString(rdsColumns, key, alertId, deviceId);
mapRDS.put(key, insightSQL.returnResultsAsArray(query ,rdsColumns.split(","),rdsColumns));
}
where rdsColumns are the fields I am querying in RDS data-base.
Expected: iterating over both maps and verifying at that all values according to key in the first map contains or equal in the second map.
This is the code you are looking for:
for (String keys : firstMap.keySet()) {
String[] val1 = firstMap.get(keys);
String[] val2 = secondMap.get(keys);
if (Arrays.equals(val1, val2)) {
//return true;
}
ArrayList<Boolean> contains = new ArrayList<>();
for (int i = 0; i < val1.length; i++) {
for (String[] secondMapVal : secondMap.values()) {
List<String> list = Arrays.asList(secondMapVal);
if (list.contains(val1[i])) {
contains.add(true);
break;
} else contains.add(false);
}
}
if (contains.contains(true)) {
//return true; Even a single value matches up
} else {
//return false; Not even a sinle value matches up
}
}
Basically what we have here is a HashMap<String, String>. We take the set of keys and iterate through them. Then we take the value with the key from the two sets. After we got the values we compare them and if they are the same I just print that they match. You can change this and implement this with other types of HashMaps, even where you use custom values. If I didn't understand your problem tell me and I will edit the answer.

How to see the distribution of keys in a HashMap?

When using a hash map, it's important to evenly distribute the keys over the buckets.
If all keys end up in the same bucket, you essentially end up with a list.
Is there a way to "audit" a HashMap in Java in order to see how well the keys are distributed?
I tried subtyping it and iterating Entry<K,V>[] table, but it's not visible.
I tried subtyping it and iterating Entry[] table, but it's not visible
Use Reflection API!
public class Main {
//This is to simulate instances which are not equal but go to the same bucket.
static class A {
#Override
public boolean equals(Object obj) { return false;}
#Override
public int hashCode() {return 42; }
}
public static void main(String[] args) {
//Test data
HashMap<A, String> map = new HashMap<A, String>(4);
map.put(new A(), "abc");
map.put(new A(), "def");
//Access to the internal table
Class clazz = map.getClass();
Field table = clazz.getDeclaredField("table");
table.setAccessible(true);
Map.Entry<Integer, String>[] realTable = (Map.Entry<Integer, String>[]) table.get(map);
//Iterate and do pretty printing
for (int i = 0; i < realTable.length; i++) {
System.out.println(String.format("Bucket : %d, Entry: %s", i, bucketToString(realTable[i])));
}
}
private static String bucketToString(Map.Entry<Integer, String> entry) throws Exception {
if (entry == null) return null;
StringBuilder sb = new StringBuilder();
//Access to the "next" filed of HashMap$Node
Class clazz = entry.getClass();
Field next = clazz.getDeclaredField("next");
next.setAccessible(true);
//going through the bucket
while (entry != null) {
sb.append(entry);
entry = (Map.Entry<Integer, String>) next.get(entry);
if (null != entry) sb.append(" -> ");
}
return sb.toString();
}
}
In the end you'll see something like this in STDOUT:
Bucket : 0, Entry: null
Bucket : 1, Entry: null
Bucket : 2, Entry: Main$A#2a=abc -> Main$A#2a=def
Bucket : 3, Entry: null
HashMap uses the keys produced by the hashCode() method of your key objects, so I guess you are really asking how evenly distributed those hash code values are. You can get hold of the key objects using Map.keySet().
Now, the OpenJDK and Oracle implementations of HashMap do not use the key hash codes directly, but apply another hashing function to the provided hashes before distributing them over the buckets. But you should not rely on or use this implementation detail. So you ought to ignore it. So you should just ensure that the hashCode() methods of your key values are well distributed.
Examining the actual hash codes of some sample key value objects is unlikely to tell you anything useful unless your hash cide method is very poor. You would be better doing a basic theoretical analysis of your hash code method. This is not as scary as it might sound. You may (indeed, have no choice but to do so) assume that the hash code methods of the supplied Java classes are well distributed. Then you just need a check that the means you use for combining the hash codes for your data members behaves well for the expected values of your data members. Only if your data members have values that are highly correlated in a peculiar way is this likely to be a problem.
You can use reflection to access the hidden fields:
HashMap map = ...;
// get the HashMap#table field
Field tableField = HashMap.class.getDeclaredField("table");
tableField.setAccessible(true);
Object[] table = (Object[]) tableField.get(map);
int[] counts = new int[table.length];
// get the HashMap.Node#next field
Class<?> entryClass = table.getClass().getComponentType();
Field nextField = entryClass.getDeclaredField("next");
nextField.setAccessible(true);
for (int i = 0; i < table.length; i++) {
Object e = table[i];
int count = 0;
if (e != null) {
do {
count++;
} while ((e = nextField.get(e)) != null);
}
counts[i] = count;
}
Now you have an array of the entry counts for each bucket.
Client.java
public class Client{
public static void main(String[] args) {
Map<Example, Number> m = new HashMap<>();
Example e1 = new Example(100); //point 1
Example e2 = new Example(200); //point2
Example e3 = new Example(300); //point3
m.put(e1, 10);
m.put(e2, 20);
m.put(e3, 30);
System.out.println(m);//point4
}
}
Example.java
public class Example {
int s;
Example(int s) {
this.s =s;
}
#Override
public int hashCode() {
// TODO Auto-generated method stub
return 5;
}
}
Now at point 1, point 2 and point 3 in Client.java, we are inserting 3 keys of type Example in hashmap m. Since hashcode() is overridden in Example.java, all three keys e1,e2,e3 will return same hashcode and hence same bucket in hashmap.
Now the problem is how to see the distribution of keys.
Approach :
Insert a debug point at point4 in Client.java.
Debug the java application.
Inspect m.
Inside m, you will find table array of type HashMap$Node and size 16.
This is literally the hashtable. Each index contains a linked list of Entry objects that are inserted into hashmap. Each non null index has a hash variable that correspond to the hash value returned by the hash() method of Hashmap. This hash value is then sent to indexFor() method of HashMap to find out the index of table array , where the Entry object will be inserted. (Refer #Rahul's link in comments to question to understand the concept of hash and indexFor).
For the case, taken above, if we inspect table, you will find all but one key null.
We had inserted three keys but we can see only one, i.e. all three keys have been inserted into the same bucket i.e same index of table.
Inspect the table array element(in this case it will be 5), key correspond to e1, while value correspond to 10 (point1)
next variable here points to next node of Linked list i.e. next Entry object which is (e2, 200) in our case.
So in this way you can inspect the hashmap.
Also i would recommend you to go through internal implementation of hashmap to understand HashMap by heart.
Hope it helped..

Contains operation in hashmap key

My hashmap contains one of entry as **key: its-site-of-origin-from-another-site##NOUN** and **value: its##ADJ site-of-origin-from-another-site##NOUN**
i want to get the value of this key on the basis of only key part of `"its-site-of-origin-from-another-site"``
If hashmap contains key like 'its-site-of-origin-from-another-site' then it should be first pick 'its' and then 'site-of-origin-from-another-sit' only not the part after '##'
No. It would be a String so it will pick up whatever after "##" as well. If you need value based on substring then you would have to iterate over the map like:
String value = map.get("its...");
if (value != null) {
//exact match for value
//use it
} else {//or use map or map which will reduce your search time but increase complexity
for (Map.Entry<String, String> entry : map.entrySet()) {
if (entry.getKey().startsWith("its...")) {
//that's the value i needed.
}
}
}
You can consider using a Patricia trie. It's a data structure like a TreeMap where the key is a String and any type of value. It's kind of optimal for storage because common string prefix between keys are shared, but the property which is interesting for your use case is that you can search for specific prefix and get a sorted view of the map entries.
Following is an example with Apache Common implementation.
import org.apache.commons.collections4.trie.PatriciaTrie;
public class TrieStuff {
public static void main(String[] args) {
// Build a Trie with String values (keys are always strings...)
PatriciaTrie<String> pat = new PatriciaTrie<>();
// put some key/value stuff with common prefixes
Random rnd = new Random();
String[] prefix = {"foo", "bar", "foobar", "fiz", "buz", "fizbuz"};
for (int i = 0; i < 100; i++) {
int r = rnd.nextInt(6);
String key = String.format("%s-%03d##whatever", prefix[r], i);
String value = String.format("%s##ADJ %03d##whatever", prefix[r], i);
pat.put(key, value);
}
// Search for all entries whose keys start with "fiz"
SortedMap<String, String> fiz = pat.prefixMap("fiz");
fiz.entrySet().stream().forEach(e -> System.out.println(e));
}
}
Prints all keys that start with "fiz" and sorted.
fiz-000##whatever
fiz-002##whatever
fiz-012##whatever
fiz-024##whatever
fiz-027##whatever
fiz-033##whatever
fiz-036##whatever
fiz-037##whatever
fiz-041##whatever
fiz-045##whatever
fiz-046##whatever
fiz-047##whatever
fizbuz-008##whatever
fizbuz-011##whatever
fizbuz-016##whatever
fizbuz-021##whatever
fizbuz-034##whatever
fizbuz-038##whatever

Changing LinkedHashMapValues

Below is data from 2 linkedHashMaps:
valueMap: { y=9.0, c=2.0, m=3.0, x=2.0}
formulaMap: { y=null, ==null, m=null, *=null, x=null, +=null, c=null, -=null, (=null, )=null, /=null}
What I want to do is input the the values from the first map into the corresponding positions in the second map. Both maps take String,Double as parameters.
Here is my attempt so far:
for(Map.Entry<String,Double> entryNumber: valueMap.entrySet()){
double doubleOfValueMap = entryNumber.getValue();
for(String StringFromValueMap: strArray){
for(Map.Entry<String,Double> entryFormula: formulaMap.entrySet()){
String StringFromFormulaMap = entryFormula.toString();
if(StringFromFormulaMap.contains(StringFromValueMap)){
entryFormula.setValue(doubleOfValueMap);
}
}
}
}
The problem with doing this is that it will set all of the values i.e. y,m,x,c to the value of the last double. Iterating through the values won't work either as the values are normally in a different order those in the formulaMap. Ideally what I need is to say is if the string in formulaMap is the same as the string in valueMap, set the value in formulaMap to the same value as in valueMap.
Let me know if you have any ideas as to what I can do?
This is quite simple:
formulaMap.putAll(valueMap);
If your value map contains key which are not contained in formulaMap, and you don't want to alter the original, do:
final Map<String, Double> map = new LinkedHashMap<String, Double>(valueMap);
map.keySet().retainAll(formulaMap.keySet());
formulaMap.putAll(map);
Edit due to comment It appears the problem was not at all what I thought, so here goes:
// The result map
for (final String key: formulaMap.keySet()) {
map.put(formulaMap.get(key), valueMap.get(key));
// Either return the new map, or do:
valueMap.clear();
valueMap.putAll(map);
for(Map.Entry<String,Double> valueFormula: valueMap.entrySet()){
formulaMap.put(valueFormula.getKey(), valueFormula.value());
}

Associate multiple values with the single key and also maintain the order of values

I have a class which contain the following members
private String patientName;
private String patientPhoneNumber;
now I have multiple names attached with the phone No. for example
1234567, AAA
1234567, BBB
8765432, CCC
8765432, GGG
Now I want to store them in a Map but the phone No. should be the key having multiple values, for 1234567 i should have value AAA and BBB, please advise how can I store the multiple values with the single key in map here my key is Phone No. and then please let me know if I want to print in console then ow would I iterate over the Map
Also please not that I want to maintain the order also let say first I get the value AAA and then BBB so i should maintain these order also, since I get this is just a example but in my scenario I will be getting this value from backend so to maintain the order is also necessary please advise.
You may try something like this:
HashMap<String,LinkedList<String>> map
private Map<String,List<String>> patients;
public void setPatientNumber(String number, String patient){
List<String> list = patients.get(number);
if(list == null){
list = new ArrayList<String>();
patients.put(number,list);
}
list.add(patient);
}
new Map<String, TreeSet<String>>()
Will allow you to store the values in a TreeSet (sorted...).
To print them:
for(Map.Entry entry : phoneBook.entries()){
System.out.println(entry.key() + ":");
TreeSet names = entry.value();
for(String name : names){
System.out.println("\t" + name);
}
}
You can add, like this, if you want case insensitive ordering:
TreeSet<String> nameSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
phoneBook.add(number, nameSet);
Use a LinkedHashMap and an ArrayList for each values :
LinkedHashMap<String,ArrayList<String>> phoneNumbers = new LinkedHashMap<String,ArrayList<String>>();
// register new phone number
phoneNumbers.put("1234567", new ArrayList<String>());
// add names to the phone number
phoneNumbers.get("1234567").add("AAA");
phoneNumbers.get("1234567").add("BBB");
Both collections preserve the insertion ordering.
** Edit **
Here, this is roughly what you'd need (this was done on the spot without much testing, but you should get the idea). Since your ordering may vary, I thought limiting duplicates and providing a comparator for ordering should be preferable (as suggested by other answers) :
public class LinkedMultiMap<K,V> {
private Comparator<V> comparator;
private LinkedHashMap<K,Set<V>> entries;
public LinkedMultiMap() {
this(null);
}
public LinkedMultiMap(Comparator<V> comparator) {
this.comparator = comparator;
this.entries = new LinkedHashMap<K, Set<V>>();
}
public boolean add(K key, V value) {
if (!entries.containsKey(key)) {
entries.put(key, new TreeSet<V>(comparator));
}
return entries.get(key).add(value);
}
public Collection<V> get(K key) {
return entries.get(key);
}
public boolean remove(K key, V value) {
boolean removed = false;
if (entries.containsKey(key)) {
removed = entries.get(key).remove(value);
if (entries.get(key).isEmpty()) {
entries.remove(key);
}
}
return removed;
}
public Collection<V> removeAll(K key) {
return entries.remove(key);
}
public Iterator<K> keyIterator() {
return entries.keySet().iterator();
}
}
Associate multiple values with the single key
That is a Multimap:
A collection similar to a Map, but which may associate multiple values with a single key.
LinkedHashMultimap from Google Collections seems to fit the bill:
Implementation of Multimap that does not allow duplicate key-value
entries and that returns collections whose iterators follow the
ordering in which the data was added to the multimap.
If you don't want to add the dependency, you can use a collection as value type:
Map<String /*number*/, List<String> /*names*/> numbers;
Note that the former only allows retrieval in order of insertion, if you want to be able to change it you will have to use the latter hand-rolled solution
Use a Map to store your data. The keys should be String objects and the values should be List objects. Using a List as the map entry value allows to associate multiple values with a single key. A List will also maintain the order of adding elements.
public static void main(String[] args) {
Map<Integer,List<String>> map = new HashMap<Integer,List<String>>();
//insert values into list one
List<String> list1 = new ArrayList<String>();
list1.add("AAA");
list1.add("BBB");
//insert values into list one
List<String> list2 = new ArrayList<String>();
list2.add("CCC");
list2.add("GGG");
//insert values to keys
//single key multiple values
map.put(1234567, list1);
map.put(8765432, list2);
//iterating and displaying the values
for(Map.Entry<Integer,List<String>> entry: map.entrySet() ) {
Integer key = entry.getKey();
List<String> values = entry.getValue();
System.out.println("Value of " +key+ " is " +values);
//System.out.println("Value of " +key+ " is " +values.get(0));
//System.out.println("Value of " +key+ " is " +values.get(1));
}
}

Categories

Resources