41

I have a LinkedHashMap (called info) that contains name/age (string/int) pairs. How can I get the position of the key/value if I input the key? For example, if my LinkedHashMap looked like this {bob=12, jeremy=42, carly=21} and I was to search jeremy, it should return 1 as its in position 1. I was hoping I can use something like info.getIndex("jeremy").

Ruli
  • 2,592
  • 12
  • 30
  • 40
M9A
  • 3,168
  • 14
  • 51
  • 79
  • 1
    See [why-doesnt-linkedhashmap-provide-access-by-index](http://stackoverflow.com/questions/5666820/why-doesnt-linkedhashmap-provide-access-by-index) – nawfal Jun 30 '14 at 15:37

7 Answers7

32

HashMap implementations in general are un-ordered for Iteration.

LinkedHashMap is predictablely ordered for Iteration ( insertion order ) but does not expose the List interface and a LinkedList ( which is what mirrors the key set insertion order ) does not track index position itself either, it is very in-efficient to find the index as well. The LinkedHashMap doesn't expose the reference to the internal LinkedList either.

The actual "Linked List" behavior is implementation specific. Some may actually use an instance of LinkedList some many just have Entry track a previous and next Entry and use that as its implementation. Don't assume anything without looking at the source.

The KeySet that contains the keys does not guarantee order as well because of the hashing algorithms used for placement in the backing data structure of the inherited HashMap. So you can't use that.

The only way to do this, without writing your own implementation, is to walk the Iterator which uses the mirroring LinkedList and keep a count where you are, this will be very in-efficient with large data sets.

Solution

What it sounds like you want is original insertion order index positions, you would have to mirror the keys in the KeySet in something like an ArrayList, keep it in sync with updates to the HashMap and use it for finding position. Creating a sub-class of HashMap, say IndexedHashMap and adding this ArrayList internally and adding a .getKeyIndex(<K> key) that delegates to the internal ArrayList .indexOf() is probably the best way to go about this.

This is what LinkedHashMap does but with a LinkedList mirroring the KeySet instead of an ArrayList.

  • 1
    I thought LinkedHashMap retains the order. Is there any thing I can use that can store key/value but retain the order? – M9A Apr 30 '12 at 16:48
  • 6
    It retains, *order* but does not track *position*. –  Apr 30 '12 at 17:08
  • @HernánEche read the question and the answer for comprehension, they want to track position as well, which I go into detail about in my answer. –  Jan 23 '13 at 20:05
  • @HernánEche the *order* of a `HashSet` *can* change on every insert or deletion, it isn't guaranteed, so yes I am correct, you can't use that! –  Feb 07 '14 at 20:10
28
int pos = new ArrayList<String>(info.keySet()).indexOf("jeremy")
marioosh
  • 27,328
  • 49
  • 143
  • 192
  • 9
    Ignore the above comments. `keySet()` order for `LinkedHashMap` is guaranteed. https://stackoverflow.com/questions/2923856/is-the-order-guaranteed-for-the-return-of-keys-and-values-from-a-linkedhashmap-o. – ykaganovich Dec 14 '18 at 23:20
5

I saw a suggestion from one of the duplicates of this question at

How get value from LinkedHashMap based on index not on key?

and I liked the suggestion as described as pseudo code from @schippi in the comments. I thought some working Java code might be useful to others on this approach

import java.util.ArrayList;
import java.util.LinkedHashMap;

public class IndexedLinkedHashMap<K,V> extends LinkedHashMap<K,V> {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    ArrayList<K> al_Index = new ArrayList<K>();

    @Override
    public V put(K key,V val) {
        if (!super.containsKey(key)) al_Index.add(key);
        V returnValue = super.put(key,val);
        return returnValue;
    }

    public V getValueAtIndex(int i){
        return (V) super.get(al_Index.get(i));
    }

    public K getKeyAtIndex(int i) {
        return (K) al_Index.get(i);
    }

    public int getIndexOf(K key) {
        return al_Index.indexOf(key);
    }

}
Rocksalt
  • 160
  • 1
  • 7
1

Considering that LinkedHashMap keep the order of insertion, you can use the keySet() and List.copyOf() (since Java 10) methods like this:

List<String> keys = List.copyOf( yourLinkedHashMap.keySet() );

System.out.println( keys.indexOf("jeremy") ); // prints '1'
0

LinkedHashMap has "predictable iteration order" (javadoc). Items don't know their location, though, so you'll have to iterate the collection to get it. If you're maintaining a large map you may want to use a different structure for storage.

Edit: clarified iteration

Mike D
  • 64
  • 7
  • *"walking the key `Set` won't do you any good, it is backed by a `Set`, and it is un-ordered. The `LinkedList` is only used for the `Iterator`. [When in doubt use the source](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.keySet%28%29). –  Apr 30 '12 at 17:02
  • Indeed. I meant entry set. That's why a quoted the iteration order bit...implied iterator usage. Poor choice of words on my part. – Mike D Apr 30 '12 at 17:12
  • The first paragraph in the javadoc says it's in insert order, which is exactly what he asked for. I think we're saying the same thing here, anyway. The edits have made your answer clearer. – Mike D Apr 30 '12 at 18:32
  • they want the position by **key** in insertion order, you can't get that without using the `Iterator` with a stock `LinkedHashMap`. Neither the keyset or entryset will individually give what they are asking for. –  Apr 30 '12 at 18:36
0

You can use com.google.common.collect.LinkedListMultimap from the Google Guava library. You don't need the multimap behaviour of this class what you want is that the keys() method guarantees they are returned in insertion order and can then be used to construct a List, you can use the indexOf() to find the required index position

Mohammad Faisal
  • 5,783
  • 15
  • 70
  • 117
Paul Taylor
  • 13,411
  • 42
  • 184
  • 351
0

I do extract the positons of the key into a concurent map like this:

Here for a Map, someListOfComplexObject() would be entrySet() and getComplexStringKeyElem() would be getKey()

might come from

 final int[] index = {0};
 Stream<ComplexObject> t = someListOfComplexObject.stream();
 ConcurrentMap<String, List<Integer>> m = 
      t.collect(Collectors.groupingBy(
           e -> e.getComplexStringKeyElem(),
           Collectors.mapping(
                e -> index[0]++,
                Collectors.toList()
           ),
           ConcurrentSkipListMap::new));
user1767316
  • 3,276
  • 3
  • 37
  • 46