Is it possible in Guava,
To conduct a reverse lookup in BiMap for key and multiple values? Precisely, I have key and corresponding multiple values, I want to get key from a value.
To store multiple values in LinkedHashMap? Precisely, I want to store, key - multiple values in some order thus I can get key position in the list.
Ad. 1. Yes, it's possible to do a reverse lookup with a BiMap<K, V>, you just call inverse on your BiMap and you get inversed BiMap<V, K> view of your BiMap.
Example (taken from Guava's test suite):
public void testMapConstructor() {
/* Test with non-empty Map. */
Map<String, String> map = ImmutableMap.of(
"canada", "dollar",
"chile", "peso",
"switzerland", "franc");
HashBiMap<String, String> bimap = HashBiMap.create(map);
assertEquals("dollar", bimap.get("canada"));
assertEquals("canada", bimap.inverse().get("dollar"));
}
Ad. 2. Assuming you mean "I want to store, key -> multiple [collection] values" (Map<K, Collection<V>>), ListMultimap is probably what you want, more precisly ArrayListMultimap (preserves values order) or LinkedListMultimap (preserves both keys and values order). If your object is going to be immutable, I strongly advice you use ImmutableListMultimap.
You can also create your own implementation of Multimap by using factory (bit verbose), i.e. I use:
private static <K, V> ListMultimap<K, V> makeLinkedArrayListMultimap() {
return Multimaps.newListMultimap(Maps.<K, Collection<V>>newLinkedHashMap(),
new Supplier<List<V>>() {
#Override public List<V> get() {
return Lists.newArrayList();
}
});
}
public static void main(final String[] args) {
final ListMultimap<String, String> multimap = makeLinkedArrayListMultimap();
multimap.putAll("one", ImmutableList.of("zero", "three"));
multimap.putAll("two", ImmutableList.of("three", "four", "three"));
multimap.putAll("three", ImmutableList.<String>of()); // note that this doesn't add key to multimap
multimap.put("four", "forty-two");
System.out.println(multimap);
// prints {one=[one, three], two=[three, four, three], four=[forty-two]}
final List<String> listForOnes = multimap.get("one");
System.out.println(listForOnes.get(0));
// prints zero
}
P.S. Take a look at Guava's wiki, which is explaining both BiMap and Multimap.
The closest in Guava is Multiset to map multiple values to key, but I doubt it satisfies your requirement.
I doubt it is good idea to look up key using values (when you have multiple values mapped to single key), in order to do this your value should be unique and considering your data structure (which is like Map<Key, Collection<Value>) it cannot be guaranteed to have unique values.
The another option with guava is BiMap which requires unique values and can provide a reverse mappings (value -> key) but since you need to map multiple values to same key, this also not a good fit.
As #Xaerxess says in his answer to your 2nd question, you can make your own ListMultimap that uses a LinkedHashMap as its backing map using the Multimaps.newListMultimap method.
For your 1st question, where you have keys mapped to multiple values (i.e. a Multimap), you can use the method Multimaps.invertFrom to create an inverted copy of your original Multimap to do inverse lookups on. Also, you can create an ImmutableListMultimap copy of the original and use its inverse() method to get the inverse, though that's just going to copy the original just like Multimaps.invertFrom does (though it will cache it so repeated calls to inverse() return the same copy.)
This is likely worth it if you don't mind the extra memory consumption, are going to want to do multiple inverse lookups, and don't need the inverse copy to stay up to date with changes to the original that happen after you create it. If you just want to lookup the keys that map to one specific value, you can do that in one iteration of the entries without creating a full copy.
Related
I have multiple files which contains key=value string pairs. The keys are the same between the files, but the values differs. Each file can have 1000 plus of such pairs.
I want to store each file in a separate hashmap, ie map<KeyString, ValueString>, so if there are five files, then there will be five hashmaps.
To avoid duplicating the keys across each hashmap, is it possible to have each map reference the same key? Note that once the keys are added to the map, it will not be deleted.
I considered making the first file the 'base' as in the flyweight pattern, this base would be the intrinsic set of keys/values. The other remaining files would be the extrinsic set of values, but I don't know how to relate the values back to the base (intrinsic) keys without duplicating the keys?
I am open to a simpler/better approach.
I can think about a simpler approach. Instead of having Map<String, String> think of Map<String, List<String> or directly MultiMap<String, String> from guava.
If each key is in each file and all have values, you could store values from first file at 0th index, from the second at 1st index etc.
If it wouldn't work, I recommend a Collection<Map<String, String>, so you're able to iterate through your Maps. Then when you want to add value to one of the Maps, go through all keySets and if one of them contains that key, just put with object returned from this keySet.
Other solution would be to have a HashSet of keys that have already been put. This would be more efficient.
After reading in the keys, you can use String.intern().
When called, what it does is either:
add the String to the internal pool if it didn't exist already;
return the equivalent String from the pool if it already existed.
String#intern Javadoc
First of all, I don't see the problem with storing multiple instances of your String keys. 5 HashMaps * 1000 keys is a very small number, and you shouldn't have memory issues.
That said, if you still want to avoid duplicating the Strings, you can create the first HashMap, and then you the exact same keys for the other HashMaps.
For example, suppose map1 is the first HashMap and it is already populated with the contents of the first file.
You can write something like this to populate the 2nd HashMap:
for (String key : map1.keySet()) {
map2.put (key, someValue);
}
Of course you will have to find for each key of the first map the corresponding value of the second map. If the keys are not stored in the same order in the input files, this may require some preliminary sorting step.
Perhaps you could hold a static Map<> to map your keys to unique Integers and use those Integers for the keys to your map?
Something like:
class KeySharedMap<K,V> {
// The next key to use. Using Atomics for the auto-increment.
static final AtomicInteger next = new AtomicInteger(0);
// Static mapping of keys to unique Integers.
static final ConcurrentMap<Object,Integer> keys = new ConcurrentHashMap<>();
// The map indexed by Integer from the `keys`.
Map<Integer, V> map = new HashMap<>();
public V get(Object key) {
return map.get(keys.get(key));
}
public V put(Object key, V value) {
// Associate a unique integer for each unique key.
keys.computeIfAbsent(key,x -> next.getAndIncrement());
// Put it in my map.
return map.put(keys.get(key),value);
}
}
Yes, I realise that K is not used here but I suspect it would be necessary if you wish to implement Map<K,V>.
I basically need to know if my HashMap has different keys that map to the same value. I was wondering if there is a way other than checking each keys value against all other values in the map.
Update:
Just some more information that will hopefully clarify what I'm trying to accomplish. Consider a String "azza". Say that I'm iterating over this String and storing each character as a key, and it's corresponding value is some other String. Let's say I eventually get to the last occurrence of 'a' and the value is already be in the map.This would be fine if the key corresponding with the value that is already in the map is also 'a'. My issue occurs when 'a' and 'z' both map to the same value. Only if different keys map to the same value.
Sure, the fastest to both code and execute is:
boolean hasDupeValues = new HashSet<>(map.values()).size() != map.size();
which executes in O(n) time.
Sets don't allow duplicates, so the set will be smaller than the values list if there are dupes.
Very similar to EJP's and Bohemian's answer above but with streams:
boolean hasDupeValues = map.values().stream().distinct().count() != map.size();
You could create a HashMap that maps values to lists of keys. This would take more space and require (slightly) more complex code, but with the benefit of greatly higher efficiency (amortized O(1) vs. O(n) for the method of just looping all values).
For example, say you currently have HashMap<Key, Value> map1, and you want to know which keys have the same value. You create another map, HashMap<Value, List<Key>> map2.
Then you just modify map1 and map2 together.
map1.put(key, value);
if(!map2.containsKey(value)) {
map2.put(value, new ArrayList<Key>);
}
map2.get(value).add(key);
Then to get all keys that map to value, you just do map2.get(value).
If you need to put/remove in many different places, to make sure that you don't forget to use map2 you could create your own data structure (i.e. a separate class) that contains 2 maps and implement put/remove/get/etc. for that.
Edit: I may have misunderstood the question. If you don't need an actual list of keys, just a simple "yes/no" answer to "does the map already contain this value?", and you want something better than O(n), you could keep a separate HashMap<Value, Integer> that simply counts up how many times the value occurs in the map. This would take considerably less space than a map of lists.
You can check whether a map contains a value already by calling map.values().contains(value). This is not as efficient as looking up a key in the map, but still, it's O(n), and you don't need to create a new set just in order to count its elements.
However, what you seem to need is a BiMap. There is no such thing in the Java standard library, but you can build one relatively easily by using two HashMaps: one which maps keys to values and one which maps values to keys. Every time you map a key to a value, you can then check in amortized O(1) whether the value already is mapped to, and if it isn't, map the key to the value in the one map and the value to the key in the other.
If it is an option to create a new dependency for your project, some third-party libraries contain ready-made bimaps, such as Guava (BiMap) and Apache Commons (BidiMap).
You could iterate over the keys and save the current value in the Set.
But, before inserting that value in a Set, check if the Set already contains that value.
If this is true, it means that a previous key already contains the same value.
Map<Integer, String> map = new HashMap<>();
Set<String> values = new HashSet<>();
Set<Integter> keysWithSameValue = new HashSet<>();
for(Integer key : map.keySet()) {
if(values.contains(map.get(key))) {
keysWithSameValue.add(key);
}
values.add(map.get(key));
}
I need a data structure that provides key-value mappings, like a Map, but that also allows me to fetch the key based on an (int) index (e.g. myKey = myDS.get(index)), without having to iterate over the data structure to get the key at the desired index.
I thought of using LinkedHashMap, but I don't see a way to get the key at a given index. Am I missing something in LinkedHashMap? Or is there another data structure I can use?
EDIT:
This is not a duplicate. The correct answer to the other question is to use some sort of SortedMap; however, that is not the correct answer to this question, since I'd like to be able to retrieve an Entry from the data structure via an Integer index, which is not supported in any Java library.
LinkedHashMap provides a hash table/doubly linked list implementation of the Map interface. Since it extends HashMap, it's still backed by an array, but there is also a doubly-linked list of Entry objects to ensure that the iteration order is predictable.
So, basically what it means is that when you iterate through the map like so:
for (Map.Entry<keyType,valueType>> entry : linkedHashMap.entrySet())
{
System.out.println("Key: " + entry.getKey().toString() +
" Value: " + entry.getValue.toString());
}
it will print in the order that you added the keys, as opposed to a non-linked Map, which will not print in insertion order. You cannot access the elements of the array like you want to, because the array that backs the hash is not in order. Only the doubly linked list is ordered.
Solution:
What you are looking for is a LinkedMap from Apache Commons.
AFAIK, there is no single data structure that will do this. There is certainly not one in the standard Java collection suite.
Also LinkedHashMap is not the solution because you cannot efficiently index a LinkedHashMap.
If you want to do index-based lookup as well as keep-based lookup, solution needs to be a combination of two data structures.
A Map<Key, Value> and an ArrayList<Value> is the simpler approach, but it has a couple of problems:
- Insertion and deletion of values from the ArrayList is expensive, unless you are inserting / deleting at the tail end of the list.
- Insertion and deletion makes the list positions unstable,.
If you want stable indexes and scalable insertion and deletion, then you need a Map<Key, Value> and a Map<Integer, Value> ... and a way to manage (i.e. recycle) the index values.
The Apache Commons LinkedMap class is a possible solution, except that it suffers from the problem that index values are not stable in the face of insertions and deletions.
How about using:
Map<String, String> map = new LinkedHashMap<String, String>();
List<Entry<String, String>> mapAsList = new ArrayList<Map.Entry<String,String>>(map.entrySet());
mapAsList.get(index);
I do not believe there is a collection for this; collections are either based on the idea that you want to know exactly where an element is (lists) or that you want quick access based on some key or criteria (maps); doing both would be very resource-intensive to maintain.
Of course, you can make something like this, as rocketboy's answer suggests, but I'm guessing it's not really possible to make efficient.
There is no direct DS in the standard Java Collections API to provide a indexed map. However, the following should let you achieve the result:
// An ordered map
Map<K, V> map = new LinkedHashMap<K, V>();
// To create indexed list, copy the references into an ArrayList (backed by an array)
List<Entry<K, V>> indexedList = new ArrayList<Map.Entry<K, V>>(map.entrySet());
// Get the i'th term
<Map.Entry<K,V>> entry = indexedList.get(index);
K key = entry.getKey();
V value = entry.getValue();
You might still want to retain the concerns of data persistence in the map separate from the retrieval.
Update:
Or use LinkedMap from Apache Commons as suggested by Steve.
I want to make the following types of objects. This is my higher-level desire that I'd like to figure out in Java:
ListObject(key, String): every key corresponds to a String value; key is a string itself
ListObject(key, String[]): every key corresponds to an array of Strings; key is a string itself
ListObject(key, String, String[]): same deal but with two value fields per key.
How would I make (and use!) objects of this type?
Thanks.
You seem to need some Maps rather than Lists. Check the Javadoc for Map implementations; the most common is HashMap, but there are sorted, concurrent, deterministically iterable implementations etc. available too.
ListObject: every key corresponds to a String value; key is a string itself
Map<String, String>
ListObject: every key corresponds to an array of Strings; key is a string itself
Map<String, String[]>
(or preferably Map<String, List<String>>)
ListObject: same deal but with two value fields per key.
Map<String, UserDefinedClassWithTwoFields>
Map<KeyType,ValueType> which is implemented by HashMap<KeyType, ValueType> and TreeMap<KeyType, ValueType>, among others -- HashMap is unordered and TreeMap is ordered.
Other useful Maps are LinkedHashMap which is like HashMap but iterates in insertion order, and com.google.common.collect.Maps in Guava which has a bunch of utility methods, and com.google.common.collect.ImmutableMap which is an immutable map implementation.
For your key corresponding to an array of strings, you might want to look at a Multimap which is a map with multiple values for a given key.
You could use Map for this purpose.
The general syntax for Map is:
Map<String, SomeObject> = new HashMap<String, SomeObject>();
Now there are four kinds of Maps in java:
HASH MAP - Use this map when you don't care about the order in which elements are displayed when you iterate over the map.
HASH TABLE - Synchronized version of hash map.
LINKED HASH MAP - Use this when you care about the insertion order.
TREE MAP - Use this when you want custom sort order.
Map are used to create associative arrays in Java.
Map for your first example. Each String key is associated to a String value.
Map for your second example. Values are arrays of String.
For your last example, you have to create you own class with two fields: one a String and one a String[]. Then, create a map that associates String to an object of your type.
Java's Map type would most likely do the trick. A Map<KeyType, ValueType> stores key-value pairs, so you would have a Map<String, String>, a Map<String, List<String>>, and a Map<String, SomePairType>.
Map is just an interface: you have to pick an implementation. HashMap and TreeMap are your best bets. Both are good, but TreeMap will only work with comparable key types.
For keys, since String is comparable with other Strings, you could use either map implementation.
I have a list of strings and I have a function to generate a value for each key in the list.
I want to create a map using this function. Can I do this with Google collections?
Use Maps.uniqueIndex(Iterable, Function) :
Returns an immutable map for which the
Map.values() are the given elements in
the given order, and each key is the
product of invoking a supplied
function on its corresponding value.(from javadoc)
Example:
Map<String,String> mappedRoles = Maps.uniqueIndex(yourList, new Function<String,String>() {
public String apply(String from) {
// do stuff here
return result;
}});
As of 7/26/2012, Guava master contains two new ways to do this. They should be in release 14.0.
Maps.asMap(Set<K>, Function<? super K, V>) (and two overloads for SortedSet and NavigableSet) allows you to view a Set plus a Function as a Map where the value for each key in the set is the result of applying the function to that key. The result is a view, so it doesn't copy the input set and the Map result will change as the set does and vice versa.
Maps.toMap(Iterable<K>, Function<? super K, V>) takes an Iterable and eagerly converts it to an ImmutableMap where the distinct elements of the iterable are the keys and the values are the results of applying the function to each key.
EDIT: It's entirely possible that Sean's right and I misunderstood the question.
If the original list is meant to be keys, then it sounds like you might be able to just use a computing map, via MapMaker.makeComputingMap, and ignore the input list to start with. EDIT: As noted in comments, this is now deprecated and deleted in Guava 15.0. Have a look at CacheBuilder instead.
On the other hand, that also doesn't give you a map which will return null if you ask it for a value corresponding to a key which wasn't in the list to start with. It also won't give you In other words, this may well not be appropriate, but it's worth consideration, depending on what you're trying to do with it. :)
I'll leave this answer here unless you comment that neither approach here is useful to you, in which case I'll delete it.
Original answer
Using Guava you can do this pretty easily with Maps.uniqueIndex:
Map<String, String> map = Maps.uniqueIndex(list, keyProjection);
(I mentioned Guava specifically as opposed to Google collections, as I haven't checked whether the older Google collections repository includes Maps.uniqueIndex.)
Either I have misunderstood you or the other posters have. I understand that you want your list to be the map keys, while Maps.uniqueIndex() creates keys to map to your values (which is quite the opposite).
Anyway, there is an open Guava issue that requests the exact functionality you are requesting, and I have also implemented such a solution for a previous question.
Using Guava + Lambda
Map<String, YourCustomClass> map = Maps.uniqueIndex(YourList, YourCustomClass -> YourCustomClass.getKey());