简体   繁体   中英

complex collection in java

I need to define a collection of user defined objects with the following access methods:

  1. access by the object's primary key (unique)
  2. access by index of the collection sorted on a secondary key (not unique)

Each object has a primary key and a secondary key (not guaranteed unique). When an object is added to the collection it should be inserted at a position so that the secondary key value is sorted. Each object should be extracted or deleted using either the primary key or the position in the collection.

What is the best way to implement this collection? (a map of the primary key and object would provide half of the access, and a sorted list would provide the other half. How can these be combined for my requirement?)

You could easily write such a collention by yourself, where you have a (plain) HashMap of just primary keys to values, and then another one of a PriorityQueue of pairs of secondary keys to primary keys (seeing as secondary keys are not always unique, so you can't use a Map type for that).

Solution:

Here is a full, working example:

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

public class MyCollection<T, TPrimaryKey, TSecondaryKey extends Comparable<TSecondaryKey>> {
    private HashMap<TPrimaryKey, T> primaryKeyMap;
    private List<PrimarySecondaryKeyPair> primarySecondaryKeyList;

    public MyCollection() {
        primaryKeyMap = new HashMap<TPrimaryKey, T>();
        primarySecondaryKeyList = new ArrayList<PrimarySecondaryKeyPair>();
    }

    public MyCollection(int initialCapacity) {
        primaryKeyMap = new HashMap<TPrimaryKey, T>(initialCapacity);
        primarySecondaryKeyList = new ArrayList<PrimarySecondaryKeyPair>(initialCapacity);
    }

    private class PrimarySecondaryKeyPair implements Comparable<PrimarySecondaryKeyPair> {
        public TPrimaryKey primaryKey;
        public TSecondaryKey secondaryKey;

        public PrimarySecondaryKeyPair(TPrimaryKey primaryKey, TSecondaryKey secondaryKey) {
            this.primaryKey = primaryKey;
            this.secondaryKey = secondaryKey;
        }

        @Override
        public int compareTo(MyCollection<T, TPrimaryKey, TSecondaryKey>.PrimarySecondaryKeyPair o) {
            return secondaryKey.compareTo(o.secondaryKey);
        }
    }

    public void put(T object, TPrimaryKey primaryKey, TSecondaryKey secondaryKey) { // put by primaryKey
        primaryKeyMap.put(primaryKey, object);
        PrimarySecondaryKeyPair keyPair = new PrimarySecondaryKeyPair(primaryKey, secondaryKey);
        int insertionPoint = Collections.binarySearch(primarySecondaryKeyList, keyPair);
        primarySecondaryKeyList.add((insertionPoint > -1) ? insertionPoint : (-insertionPoint) - 1, keyPair);
    }

    public T getByPrimaryKey(TPrimaryKey primaryKey) {
        return primaryKeyMap.get(primaryKey);
    }

    public T getByIndex(int index) {
        return getByPrimaryKey(primarySecondaryKeyList.get(index).primaryKey);
    }

    public void deleteByPrimaryKey(TPrimaryKey primaryKey) {
        primaryKeyMap.remove(primaryKey);
        for (int i = 0; i < primarySecondaryKeyList.size(); i++) {
            if (primarySecondaryKeyList.get(i).primaryKey.equals(primaryKey)) {
                primarySecondaryKeyList.remove(i);
            }
        }
    }

    public void deleteByIndex(int index) {
        primaryKeyMap.remove(primarySecondaryKeyList.remove(index).primaryKey);
    }
}

This class (I think) does what you want and it doesn't store anything extra unnecessarily.

Time complexity:

  • O(log n) time for put() (in order to sort by secondary key).

    From the JavaDoc on the Collections.binarySearch() method that this uses:

    This method runs in log(n) time for a "random access" list (which provides near-constant-time positional access).

    ( primarySecondaryKeyList is a "random access" list because it uses ArrayList ).

  • An average O(1) time for getting objects by the primary key (worst-case becomes O(n) if your primary key type has a really bad hash function).
    (see https://en.wikipedia.org/wiki/Hash_table )

  • An average O(1) time for getting by index sorted by secondary key, because that is based off of getting the primary key (this has to be done unless you want to store every object in the collection twice, which would take up twice the space).
  • Constant O(1) time for deleting by index sorted by secondary key.
  • O(n) worst-case for deleting by primary key (deleteing from primary key map is easy, but the secondary-to-primary key list has to be iterated over)

However, note that the worst case O(n) will never happen if your primary key is simply an Integer or a Long , because their hash functions are simply their numerical value, making them extremely good accurate (generally resulting in O(1) time for get() s).

Usage:

The following examples assume that you are storing String s with Long s as primary keys , and with Integer s as secondary keys . The class is generic, however, so you can store any type of object with any type of primary key (just make sure it has a decent hashCode() function!) and any secondary key that is Comparable to itself (all the primitive Number wrappers, like Integer , Long , Short , etc. are already Comparable to themselves).

To initialize with default initial capacity:

MyCollection<String, Long, Integer> myCollection = new MyCollection<String, Long, Integer>();

Initial capacity of 50:

MyCollection<String, Long, Integer> myCollection = new MyCollection<String, Long, Integer>(50);

Put the string "hello" with primary key 937234L and secondary key 2 :

myCollection.put("hello", 937234L, 2);

... everything else should be self explanatory ;)


That should meet the requirements and the type of collection you wanted.
Hope it helped. :)

(a map of the primary key and object would provide half of the access, and a sorted list would provide the other half. How can these be combined for my requirement?)

public class YourCollection{
   private Map<UserObject> yourMap;
   private List<UserObject> yourList;

   public void add(UserObject obj){
      yourMap.put(obj.primaryKey, obj);
      yourList.add(obj);
      //sort list on secondaryKey
   }

   public UserObject get(String key){
      return yourMap.get(key);
   }

   public UserObject get(int index){
      return yourList.get(index);
   }
}

This is just the basic shell, but the idea is there. Which type of List and which type of Map you choose is up to you. You could/should also add generics to this class.

Edit- Also, there is no best approach to a given problem. You have to consider the pros and cons of different approaches and weigh them against your goals. For example, do you need this to take up the least amount of memory? Do you need it to use the least amount of CPU cycles? Do you need it to be the easiest code to write and maintain? Or are you just trying to put something together that works? I'm not actually asking you to answer those questions, but you should be considering them when thinking about this type of problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM