How to create a custom iterator for a Map implementation? - java

I implemented a unique map. It's a hashmap which is bi-directional, where not only keys are unique but values too.
public interface UniqueMap<K,V>{
V uniquePut(K key, V value);
UniqueMap<V,K> inverse();
}
This is a possible implementation:
public class SimpleUniqueMap<K,V> implements UniqueMap<K,V>, Iterable<K>{
public HashMap<K,V> uniqueMap = new HashMap<K,V>();
class EnumSimpleUniqueMap implements Iterator<K>{
int count = uniqueMap.size();
public boolean hasNext(){
return count > 0;
}
public K next(){
if(count == 0){
throw new NoSuchElementException();
}else{
count--;
//...
}
}
public void remove(){
throw new UnsupportedOperationException();
}
}
public Iterator<V> iterator(){
return new EnumSimpleUniqueMap();
}
public V uniquePut(K key, V value){
return null;
}
public UniqueMap<V,K> inverse(){
return null;
}
}
As you can see I already tried to implement an iterator for my unique map. But from a hashmap values are not accessed by position but by key. So usually I would take the counter and access values but in this case it is not possibe.
Actually it would be enough to iterate over the keys and retrieve them one by another. How can I do that? Is there a way to retrieve some kind of entry object containing both key and value?
I know that I can retrieve the iterator from an map object but this is not an option for me.

UPDATE: Most simple of all, use
org.apache.commons.collections.BidiMap
But if your really want to roll your own, then consider this:
Usually, Maps don't implement Iterable. In your case, you can get the Iterator for free by calling any of these
map.keys().iterator(); // is the same as
map.inverse().values().iterator();
map.values().iterator(); // is the same as
map.inverse().keys().iterator();
map.entrySet().iterator(); // almost the same as
map.inverse().entrySet().iterator();
on your map, depending on what you want to iterate over. For that, you'd have to make
public interface UniqueMap<K,V> extends Map<K, V> {
// no need for uniquePut(), you already have Map.put()
UniqueMap<V,K> inverse();
}
It's also a good idea to make your implementation extend
java.util.AbstractMap<K, V>
Which has a lot of base functionality for maps already.

You could implement your iterator() method by simply delegating to your backing hashmap's keyset iterator:
public Iterator<K> iterator(){
return uniqueMap.keySet().iterator();
}
Of course, as Lukas said, usually a map will not be iterable, but provide collection views which themselves are iterable.
Also, it might be a good idea for your unique map implementation to have HashMaps in both directions.
Also, think about (and specify it in the interface): What should happen if the user inserts a new key with an already existing value - does this fail, get ignored, remove the existing mapping, or what?

You should have a look to the Guava library (Google Collection). They have a BiMap implementation that seems to be exactly what your are trying to implement...

Related

How to get element position from Java Map

I have this Java Map:
Can you tell me how I can get the 6-th element of the Map?
private static final Map<String, Users> cache = new HashMap<>();
is this possible? Or I have to use another Java collection?
Though a bit late to answer. But the option is to use LinkedHashMap: this map preserves the order according to insertion of elements, as everyone has suggested. However, As a warning, it has a constructor LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) which will create a linked hash map whose order of iteration is the order in which its entries were last accessed. Don't use this constructor for this case.
However, if I needed such functionality, i would extend it and implement my necessary function to re-use them in OOP way.
class MyLinkedMap<K, V> extends LinkedHashMap<K, V>
{
public V getValue(int i)
{
Map.Entry<K, V>entry = this.getEntry(i);
if(entry == null) return null;
return entry.getValue();
}
public Map.Entry<K, V> getEntry(int i)
{
// check if negetive index provided
Set<Map.Entry<K,V>>entries = entrySet();
int j = 0;
for(Map.Entry<K, V>entry : entries)
if(j++ == i)return entry;
return null;
}
}
Now i can instantiate it and can get a entry and value either way i want:
MyLinkedMap<String, Integer>map = new MyLinkedMap<>();
map.put("a first", 1);
map.put("a second", 2);
map.put("a third", 3);
System.out.println(map.getValue(2));
System.out.println(map.getEntry(1));
Output:
3
a second=2
HashMap doesn't grantee the order. If you concern about order you should use LinkedHashMap
Map<String, Users> orderedMap=new LinkedHashMap<>();
Now when you put an element it will keep the order what you put.
If you want to get 6th element, now you can do it since you have your elements in order.
orderedMap.values().toArray()[5]// will give you 6th value in the map.
Example
Map<String, String> orderedMap=new LinkedHashMap<>();
orderedMap.put("a","a");
orderedMap.put("b","b");
System.out.println(orderedMap.values().toArray()[1]); // you will get b(value)
System.out.println(orderedMap.keySet().toArray()[1]); // you will get b(key)
}
A HashMap does not maintain the order of the elements inserted in it. You can used a LinkedHashMap instead which maintains the order of the elements inserted in it.
Though you need to note that even a LinkedHashMap has no such method which would give the element at a particular index. You will have to manually iterate through the entries and extract the element at the 6th iteration.
With guava's Iterables
Iterables.get(map.entrySet(),6);
The HashMap has no defined ordering of keys.It's Unordered.
You can use LinkedHashMap which will store your keys in order of insertion.You can retrieve them by calling keySet().
HashMaps do not preserve ordering:
LinkedHashMap which guarantees a predictable iteration order.
Example
public class Users
{
private String Id;
public String getId()
{
return Id;
}
public void setId(String id)
{
Id = id;
}
}
Users user;
LinkedHashMap<String,Users> linkedHashMap = new LinkedHashMap<String,Users>();
for (int i = 0; i < 3; i++)
{
user = new Users();
user.setId("value"+i);
linkedHashMap.put("key"+i,user);
}
/* Get by position */
int pos = 1;
Users value = (new ArrayList<Users>(linkedHashMap.values())).get(pos);
System.out.println(value.getId());
According to documentation, HashMap is a Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
That's why it is not wise to use this kind of Collection.
UPDATE:
Based on #Prateek implementation of LinkedHashMap I would suggest something like:
LinkedHashMap<String,User> linkedHashMap = new LinkedHashMap<String,User>();
// or LinkedHashMap<String,User> linkedHashMap = new LinkedHashMap<>(); //for java 7+
linkedHashMap.put("1",userObj1);
linkedHashMap.put("2",userObj2);
linkedHashMap.put("3",userObj3);
/* Get by position */
int pos = 1; // Your position
User tmp= (new ArrayList<User>(linkedHashMap.values())).get(pos);
System.out.println(tmp.getName());
A HashMap doesn't have a position. You can iterate through its KeySet or EntrySet, and pick the nth element, but it's not really the same as a position. A LinkedHashMap does have a position, since it has a predictable iteration order.
You need to use a LinkedHashMap in order to be able to tell the order of the inserted elements. HashMap is not capable of doing so.
There is no Order in HashMap. You can obtain the list of may keys using map.keySet() but there's no guarantee the key set will be in the order which you add it in. Use LinkedHashMap instead of HashMap It will always return keys in same order (as insertion)
Correct!!
you will have to use other collection for getting values on index(position).
You can use ArrayList
If the ordering is to mean anything significant, you could consider using a SortedMap.
Concrete implementation: TreeMap
Use LinkedHashMap instead of HashMap It will return keys in same order (as insertion) when calling keySet().
For mare detail about LinkedHashMap see this
For example to get the element from specific index
Create a new list from your values and get the value based on index.
LinkedHashMap<String, List<String>> hMap;
List<List<String>> l = new ArrayList<List<String>>(hMap.values());
l.get(6);

Sort by one field and remove elements by another, in a Map?

Suppose there is NavigableMap<Key, Value> where Key would be:
class Key {
private String keySort;
private String keyRemove;
//getters, setters etc.
#Override
public boolean equals(Object o) {
//only include keyRemove
}
#Override
public int hashCode() {
//only include keyRemove
}
}
And there would also be a comparator:
class SortComparator implements Comparator<Key> {
#Override
public int compare(Key o1, Key o2) {
return o1.getKeySort().compareTo(o2.getKeySort());
}
}
Now the map instantiation would look like this: NavigableMap<Key, Value> myMap = new TreeMap<>(new SortComparator()); What I want is to have inside the Map, the entries sorted only by the keySort and to remove them only based on keyRemove.
The problem is that when I try to remove elements from the map, having only the keyRemove value, it does not work as expected (NullPointerException in Comparator). How can the above implementation be fixed in order to work with the expected behavior, or how can the expected behavior be implemented otherwise?
Edit: I understand what's wrong; the question remains still, for alternatives. From TreeMap JavaDoc:
Note that the ordering maintained by a sorted map (whether or not an
explicit comparator is provided) must be consistent with equals if
this sorted map is to correctly implement the Map interface. (See
Comparable or Comparator for a precise definition of consistent with
equals.)
I am thinking that when you want to remove an entry in a map, the remove method will do the job based on the equals method:
(key==null ? k==null : key.equals(k))
like described here http://docs.oracle.com/javase/6/docs/api/java/util/AbstractMap.html#remove%28java.lang.Object%29.
One solution would be to implement your own remove method by iterating through all the keys in the keyset - searching for the removeKey, but that that will replace the O(1) complexity with O(n). Maybe this can be an workaround for you.

Generic collection <K,V> with ability sorting and get by position

I know that ArrayList provide ability to get item at position get(position), and LinkedHashTable provide sorting, but it not able to get item at position. So, question: is any generic collection in java with features:
sorting
get by position
provide Key/Value generic type.
Please, give me code example of listed features, if required generic exists.
Are you looking for TreeMap or SortedMap ??
Both TreeMap and SortedMap provides:
Provides Natural Sorting of Key Element.
Is a key/value pair
Gets a value based on a Key.
BTW:
LinkedHashMap just maintains an Insertion Order, it isn't Sorted, It is just has an Order
I apologize for over complicating things earlier!
Use a TreeMap. When you want to
get(position)
just do the following:
K key = treemap.getKeys().get(position)
V value = treemap.get(key);
==========================
My old over complex incorrect answer from before:
You can use an ArrayList<Pair<K implements Comparable,V>> where you implement comparable on the Pair<K,V> and K has Comparable implemented. Then you can use Collections.sort(List<Pair<K,V>) on the Arraylist.
Then you would maintain a HashMap<K,V> so that you can retrieve elements by K. That means you will have to remember to update both data structures. That means O(N) to add an element :( . Also, to simplify the logic, you would wrap the HashMap and ArrayList in an object.
public class Pair<K extends Comparable<K>, V> implements Comparable {
private final K first;
private final V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
public K getFirst() {
return first;
}
public V getSecond() {
return second;
}
public int compareTo(K other) ; // TODO
}

Is it possible to get element from HashMap by its position?

How to retrieve an element from HashMap by its position, is it possible at all?
Use a LinkedHashMap and when you need to retrieve by position, convert the values into an ArrayList.
LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<String,String>();
/* Populate */
linkedHashMap.put("key0","value0");
linkedHashMap.put("key1","value1");
linkedHashMap.put("key2","value2");
/* Get by position */
int pos = 1;
String value = (new ArrayList<String>(linkedHashMap.values())).get(pos);
HashMaps do not preserve ordering:
This class makes no guarantees as to
the order of the map; in particular,
it does not guarantee that the order
will remain constant over time.
Take a look at LinkedHashMap, which guarantees a predictable iteration order.
If you want to maintain the order in which you added the elements to the map, use LinkedHashMap as opposed to just HashMap.
Here is an approach that will allow you to get a value by its index in the map:
public Object getElementByIndex(LinkedHashMap map,int index){
return map.get( (map.keySet().toArray())[ index ] );
}
If you, for some reason, have to stick with the hashMap, you can convert the keySet to an array and index the keys in the array to get the values in the map like so:
Object[] keys = map.keySet().toArray();
You can then access the map like:
map.get(keys[i]);
Use LinkedHashMap:
Hash table and linked list implementation of the Map interface, with predictable iteration order. This implementation differs from HashMap in that it maintains a doubly-linked list running through all of its entries.
Use LinkedHashMap and use this function.
private LinkedHashMap<Integer, String> map = new LinkedHashMap<Integer, String>();
Define like this and.
private Entry getEntry(int id){
Iterator iterator = map.entrySet().iterator();
int n = 0;
while(iterator.hasNext()){
Entry entry = (Entry) iterator.next();
if(n == id){
return entry;
}
n ++;
}
return null;
}
The function can return the selected entry.
By default, java LinkedHasMap does not support for getting value by position. So I suggest go with customized IndexedLinkedHashMap
public class IndexedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
private ArrayList<K> keysList = new ArrayList<>();
public void add(K key, V val) {
super.put(key, val);
keysList.add(key);
}
public void update(K key, V val) {
super.put(key, val);
}
public void removeItemByKey(K key) {
super.remove(key);
keysList.remove(key);
}
public void removeItemByIndex(int index) {
super.remove(keysList.get(index));
keysList.remove(index);
}
public V getItemByIndex(int i) {
return (V) super.get(keysList.get(i));
}
public int getIndexByKey(K key) {
return keysList.indexOf(key);
}
}
Then you can use this customized LinkedHasMap as
IndexedLinkedHashMap<String,UserModel> indexedLinkedHashMap=new IndexedLinkedHashMap<>();
TO add Values
indexedLinkedHashMap.add("key1",UserModel);
To getValue by index
indexedLinkedHashMap.getItemByIndex(position);
I'm assuming by 'position' you're referring to the order in which you've inserted the elements into the HashMap. In that case you want to be using a LinkedHashMap. The LinkedHashMap doesn't offer an accessor method however; you will need to write one like
public Object getElementAt(LinkedHashMap map, int index) {
for (Map.Entry entry : map.entrySet()) {
if (index-- == 0) {
return entry.value();
}
}
return null;
}
Another working approach is transforming map values into an array and then retrieve element at index. Test run of 100 000 element by index searches in LinkedHashMap of 100 000 objects using following approaches led to following results:
//My answer:
public Particle getElementByIndex(LinkedHashMap<Point, Particle> map,int index){
return map.values().toArray(new Particle[map.values().size()])[index];
} //68 965 ms
//Syd Lambert's answer:
public Particle getElementByIndex(LinkedHashMap<Point, Particle> map,int index){
return map.get( (map.keySet().toArray())[ index ] );
} //80 700 ms
All in all retrieving element by index from LinkedHashMap seems to be pretty heavy operation.
HashMap - and the underlying data structure - hash tables, do not have a notion of position. Unlike a LinkedList or Vector, the input key is transformed to a 'bucket' where the value is stored. These buckets are not ordered in a way that makes sense outside the HashMap interface and as such, the items you put into the HashMap are not in order in the sense that you would expect with the other data structures
HashMap has no concept of position so there is no way to get an object by position. Objects in Maps are set and get by keys.
HashMaps don't allow access by position, it only knows about the hash code and and it can retrieve the value if it can calculate the hash code of the key. TreeMaps have a notion of ordering. Linkedhas maps preserve the order in which they entered the map.
you can use below code to get key :
String [] keys = (String[]) item.keySet().toArray(new String[0]);
and get object or list that insert in HashMap with key of this item like this :
item.get(keys[position]);
You can try to implement something like that, look at:
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("juan", 2);
map.put("pedro", 3);
map.put("pablo", 5);
map.put("iphoncio",9)
List<String> indexes = new ArrayList<String>(map.keySet()); // <== Parse
System.out.println(indexes.indexOf("juan")); // ==> 0
System.out.println(indexes.indexOf("iphoncio")); // ==> 3

Best way to create a hashmap of arraylist

I have one million rows of data in .txt format. the format is very simple. For each row:
user1,value1
user2,value2
user3,value3
user1,value4
...
You know what I mean. For each user, it could appear many times, or appear only once (you never know). I need to find out all the values for each user. Because user may appear randomly, I used Hashmap to do it. That is: HashMap(key: String, value: ArrayList). But to add data to the arrayList, I have to constantly use HashMap get(key) to get the arrayList, add value to it, then put it back to HashMap. I feel it is not that very efficient. Anybody knows a better way to do that?
You don't need to re-add the ArrayList back to your Map. If the ArrayList already exists then just add your value to it.
An improved implementation might look like:
Map<String, Collection<String>> map = new HashMap<String, Collection<String>>();
while processing each line:
String user = user field from line
String value = value field from line
Collection<String> values = map.get(user);
if (values==null) {
values = new ArrayList<String>();
map.put(user, values)
}
values.add(value);
Follow-up April 2014 - I wrote the original answer back in 2009 when my knowledge of Google Guava was limited. In light of all that Google Guava does, I now recommend using its Multimap instead of reinvent it.
Multimap<String, String> values = HashMultimap.create();
values.put("user1", "value1");
values.put("user2", "value2");
values.put("user3", "value3");
values.put("user1", "value4");
System.out.println(values.get("user1"));
System.out.println(values.get("user2"));
System.out.println(values.get("user3"));
Outputs:
[value4, value1]
[value2]
[value3]
Use Multimap from Google Collections. It allows multiple values for the same key
https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap.html
Since Java 8 you can use map.computeIfAbsent
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#computeIfAbsent-K-java.util.function.Function-
Collection<String> values = map.computeIfAbsent(user, k -> new ArrayList<>());
values.add(value);
The ArrayList values in your HashMap are references. You don't need to "put it back to HashMap". You're operating on the object that already exists as a value in the HashMap.
If you don't want to import a library.
package util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* A simple implementation of a MultiMap. This implementation allows duplicate elements in the the
* values. (I know classes like this are out there but the ones available to me didn't work).
*/
public class MultiMap<K, V> extends HashMap<K, List<V>> {
/**
* Looks for a list that is mapped to the given key. If there is not one then a new one is created
* mapped and has the value added to it.
*
* #param key
* #param value
* #return true if the list has already been created, false if a new list is created.
*/
public boolean putOne(K key, V value) {
if (this.containsKey(key)) {
this.get(key).add(value);
return true;
} else {
List<V> values = new ArrayList<>();
values.add(value);
this.put(key, values);
return false;
}
}
}
i think what you want is the Multimap. You can get it from apache's commons collection, or google-collections.
http://commons.apache.org/collections/
http://code.google.com/p/google-collections/
"collection similar to a Map, but
which may associate multiple values
with a single key. If you call put(K,
V) twice, with the same key but
different values, the multimap
contains mappings from the key to both
values."
I Could not find any easy way. MultiMap is not always an option available. So I wrote something this.
public class Context<K, V> extends HashMap<K, V> {
public V addMulti(K paramK, V paramV) {
V value = get(paramK);
if (value == null) {
List<V> list = new ArrayList<V>();
list.add(paramV);
put(paramK, paramV);
} else if (value instanceof List<?>) {
((List<V>)value).add(paramV);
} else {
List<V> list = new ArrayList<V>();
list.add(value);
list.add(paramV);
put(paramK, (V) list);
}
return paramV;
}
}
it would be faster if you used a LinkedList instead of an ArrayList, as the ArrayList will need to resize when it nears capacity.
you will also want to appropriately estimate the capacity of the wrapping collection (HashMap or Multimap) you are creating to avoid repetitive rehashing.
As already mentioned, MultiMap is your best option.
Depending on your business requirements or constraints on the data file, you may want to consider doing a one-off sorting of it, to make it more optimised for loading.

Categories

Resources