Which data structure for fast lookup, variable length, and order? - java

I will state up front this is NOT a homework problem and is part of a hobby project I am working on. I have a working solution but I feel as though my solution is messy and space inefficient.
I have a list of items.
The length of the list is variable. Items are removed by index. Items are added to the end of the list. Items can repeat.
When an item is looked up, it is just a check to see if it is already in the list. Each item has a item-unique 'key'.
Items are sorted based on a user assigned priority level.
Right now I am using two data structures to accomplish my goals.
I have an ArrayList that is sorted based on priority (for keeping order), and an ArrayList that is sorted based on the item key (for fast lookup).
Is there a single data structure that will solve this problem? I am coding in Java if that matters. A sorted set is almost what I want, but because there are repeats I don't think this will work

Since you want lookup by key you should use a Map of some sort.
Since you want multiple objects associated with the same key you need a map of key to a list of objects rather than a map of key to a single object.
I would use a TreeMap so that it can be sorted, where the key is your key and the value is an ArrayList for the list of objects that should be returned for that key.
You will have to implement a comparator in order to get the sorting you want.
One thing to consider when using a Map of Lists is that you need to check if the map already contains a list for that key when you want to add an item.
E.g. if your map looks like this:
Map<String, List<Object>> map = new TreeMap<String, ArrayList<Object>>();
Adding an object looks like this:
public void addObject(String key, Object object) {
List<Object> objects = map.get(key);
if (objects == null) {
objects = new ArrayList<Object>();
map.put(key, objects);
}
objects.add(object);
}

What you're looking for is called a compound key in an RDBMS. If you create an ArrayList using the compound key (priority, key), you will have a single data structure.

I'm not sure about a single data structure but you can use an ArrayList<> in conjunction with a HashSet<>
Create a wrapper class that wraps an Item and a priority which I assume is an int value.
class ItemWithPriority
{
private Item item;
private int priority;
ItemWithPriority(Item item, int priority)
{
this.item = item;
this.priority = priority;
}
}
Maintain an ArrayList<ItemWithPriority> that is always sorted based on a custom Comparator<ItemWithPriority> which compares based on priority.
Additionally maintain a HashSet<Item> that contains the set of all items at a given point in time.
Insert:
itemWithPriorityList.add(new ItemWithPriority(item, priority));
Collections.sort(itemWithPriorityList, new PriorityBasedComparator());
itemSet.add(item);
Remove: (Based on index)
Item itemToBeRemoved = itemWithPriorityList.get(index);
itemWithPriorityList.remove(index);
itemSet.remove(itemToBeRemoved);
Lookup:
itemSet.contains(item)

Related

Java - Fetch key from HashMap Single Key Multiple Values (Reverse Map)

I have a HashMap defined with something like this:
Map<Foo, List<Bar>> = new HashMap();
I am trying to do a reverse search of the Hashmap using the Bar to get the Foo.
I am wanting to do something like this:
if(ArrayListBar.contains(bar)) {
return Foo;
} else {
return null;
}
Is this achievable in HashMap or is there a better way to deal with this without using the HashMap?
You can do it with Map iteration.
private Foo getKeyByValue(Map<Foo, List<Bar>> map, Bar bar){
for (Map.Entry<Foo, List<Bar>> entry : map.entrySet()){
if (entry.getValue().contains(bar)){
return entry.getKey();
}
}
return null;
}
You iterate for each entry on the map and you return the Key when the array list contains the entered bar value.
Note that your Bar class should implement the equals method so the entry.getValue().contains(bar) can be evaluated if the bar in the List with the bar on the method input are different objects.
Update: Added missing return null statement when no map element is found.
The best approach can be different under different circumstances.
You can do it by iterating the map and checking whether the list of each entry contains the value or not. But that's Ok only if the map is not too big and the lists in it or either not too big or sorted.
But if the size of the map is big or the lists are big and not sorted and you need to do multiple lookups, it is better to create a reverse map Map.
In that case you have to make sure the hashkey and equals method of Bar are implemented correctly.
If the same value (Bar) can be in multiple lists for different keys (Foo), the reverse map might also require a list of values: Map>.
Depending on the situation, you can create the reverse map while building the original map or afterwards when doing the first lookup and cache it for reuse in later lookups.
When thread safety is involved, it is preferred to create it when creating the original map because in that case you don't need to worry about thread safety of the lookup method which is a problem when creating the reverse map during the first lookup.

ArrayMap put method pushes elements in strange order

I am using ArrayMap for first time in my project and I thought it works just like an array. I expected when I use .put method it inserts it at next index.
But in my case this is not true - after I added all elements one by one the first element I added ended up at index 4 which is kind of strange.
Here are the first three steps which I add elements:
1 - Salads:
2 - Soups:
3 - Appetizers:
So somehow on second step "Soup" element was inserted in index 0 instead of 1 as I was expecting, but strangely on third step "Appetizers" was inserted as expected after "Soup".
This is the code I am using to push key and value pair:
function ArrayMap<String, DMType> addElement(String typeKey, DMType type) {
ArrayMap<String, DMType> types = new ArrayMap<>();
types.put(typeKey, type);
return types;
}
Am I missing something about the behavior of ArrayMap?
Yeah it is misleading because of the name but ArrayMap does no gurantee order unlike arrays.
ArrayMap is a generic key->value mapping data structure that is
designed to be more memory efficient than a traditional HashMap.
ArrayMap is actually a Map:
public class ArrayMap extends SimpleArrayMap implements Map
If you want the Map functionality with order guranteed use LinkedHashMap instead.
LinkedHashMap defines the iteration ordering, which is normally the
order in which keys were inserted into the map (insertion-order).
documentation
I thought it works just like an array
No, it works like a map, because it is a map. It is similar to a HashMap, but more memory efficient for smaller data sets.
It's order shouldn't and doesn't matter. Under the hood, it is implemented using
an array which has an order since arrays do. This inherently gives the ArrayMap an order, but that is not part of it's API anyway. Just like which memory slot your Java objects are in, you shouldn't care about the order here either.
It doesn't work as an array, I don't see Array in the name but Map and the documentation clearly states that behaves as a generic key->value mapping, more efficient (memory wise) than traditional HashMap implementation.
Actually I don't see why you care about the order compared to the insertion one. Data is private inside the class and you have no way to obtain the element by the index, so you are basically wondering about a private implementation which is irrelevant for its usage.
If you really want to understand how it stores its data you should take a look at the source code.
ArrayMap does NOT work like an Array, instead, it works like a HashMap with performance optimizations.
The internal sequence of the key-value pair is not guaranteed as it is NOT part of the contract.
In your case, what you really want to use is probably an ArrayList<Element>, where the Element class is defined like this:
public class Element{
private final String typeKey;
private final DMType type;
public Element(String typeKey, DMType type){
this.typeKey = typeKey;
this.type = type;
}
}
If you don't want a new Class just to store the result, and you want to keep the sequence, you can use a LinkedHashMap<String, DMType>. As the document specifies:
Class 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. This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map. (A key k is reinserted into a map m if m.put(k, v) is invoked when m.containsKey(k) would return true immediately prior to the invocation.)

What is the best way to merge similar objects in a java List?

Here is my problem (simplified):
Suppose we have a class:
public class MyClass{
String name;
Double amount;
String otherAttribute;
}
And a List<MyClass> myList
Suppose we have 2 elements from myList. Let's say object1 and object2
What I would like to do is:
if (object1.name.equals(object2.name){
//add amount of object2 to object1
//remove object 2 from the list
}
Considering I have a large list (maybe 100 elements) and I would like to find the best and less consuming way to do what I want.
What would you suggest ?
EDIT:
Yes 100 items is not large, but I would call this method (of merging similar objects) many times for many different sized lists. So that's way I would like to find the best practice for this.
I can't override equals or hashCode methods of MyClass, unfortunately (client requirement)
I'd add the objects to a HashMap where the name is the key and MyClass is the value being stored. Loop through each object in your list to add them to the map. If the name isn't in the map, just add the name, object pair. If it is already in the map, add the amount to the object already stored. When the loop completes, extract the objects from the map.
100 elements is a tiny size for a list, considering you're not going to repeat the operation some hundreds of thousands times. If it's the case, I'd consider creating a data structure indexing the list items by the search property (Map for instance), or ordering it if suitable and using an efficient search algorithm.
One approach (as suggested by Bill) would be to traverse the List adding every element to a Map, with the name property as key. You can take advantage of put's return to know if a name has been previously put into the map, and add the previosuly accumulated amounts in the current element. Finally, you could use values() to get the List without duplicates.
For instance:
List<MyClass> l;
Map<String, Myclass> m = new HashMap<MyClass>();
for (MyClass elem : l) {
MyClass oldElem = m.put(elem.getName(), elem);
if (oldElem != null) {
elem.setAmount(elem.getAmount() + oldElem.getAmount());
}
}
l = new ArrayList<MyClass>(m.values());
If you need to preserve order in the list, consider using a LinkedHashMap.
This is an O(n^2) problem unfortunately. You need to compare n elements to n-1 other elements. There is no way to do this but to brute force it.
If you used a HashMap however, you could check the map for an element before adding it to the Map which is an O(1) operation. It would look something like this:
HashMap<String, MyClass> map = new HashMap<String, MyClass>();
when you add an element:
if (map.get(obj1.name) != null) {
var obj2 = map.get(obj1.name);
obj2.amount = obj2.amount + obj1.amount;
map.put(obj1.name, obj2);
}
'Large' is relative, 100 items is definitely not large, imagine if you had to process a stream of 1.000.000 items/second. Then you would redefine large :D
In your example, what I think would be good to avoid would be to create a Set of your items' names. Searching a java HashSet takes O(1), so if an objects' name exists in the hash set, then update it on the list. An even better solution would be to create a HashMap, on which you could say e.g.
if(mymap.contains(thename)){
mymap.put(thename, newSum);
}
this being an example of how you could use it. Here's a link to get you started: http://java67.blogspot.gr/2013/02/10-examples-of-hashmap-in-java-programming-tutorial.html
I suggest to optimize (if possible) by not even doing the .add() to the list if an element with the same name exists. Using one of the hash based collections in combination with a proper equals() & hashCode() implementation based on MyClass.name should also give you somewhat good performance.
First, since you cannot override equals or hashCode, then you need to have the function that will do this functionality in the same package as your MyClass class, since no accessor methods are defined in MyClass
Second, try to have your items in a LinkedList, so that you can remove repeating elements from that list really quick without having to move around the other items.
Use a map to keep track of the amount that corresponds to a given name, while iterating the list, and removing repeating elements at the same time. In this way you don't have to create a new list.
List<MyClass> myClass_l;
Map<String, MyClass> nameMyClass_m = new HashMap<String, MyClass>();
for (Iterator<MyClass> iterator = myClass_l.iterator(); iterator.hasNext(){
MyClass m = iterator.next();
if (nameAmount_m.contains(m.name)){
MyClass firstClass = m.get(m.name);
firstClass.amount += m.amount;
iterator.remove();
}
else{
nameMyClass_m.put(m.name, m);
}
}
By the time you have finished the loop, you will have the items you want in your original list.

Optimized way to check for equality between value of Objects inside a List

I have two lists :
A : List<MyCustomObject>
B. List<MyCustomObject>
MyCustomObject has various fields. For instance a field id
In my program, I need to check if same id exists in both lists. So currently I am doing nested iterations :
for(int i=0;i<a.size();i++) {
MyCustomObject obj = a.get(i);
for(int j=0;j<b.size();j++) {
if( obj.getId().equals(b.get(j).getId()) {
//do something
break;
}
}
}
As I frequently need to do this operation, it looks me unoptimized as I am frequently iterating over long lists.
How can I optimize this operation ?
Instead of using a list you could use a Map, for example a HashMap - assuming your id is an int, it could look like:
Map<Integer, MyCustomObject> objects = new HashMap<>();
//populate
objects.put(someCustomObject.getId(), someCustomObject);
//find an id:
CustomObject obj = objects.get(someId);
note: that assumes that the ids are unique.
A linear-time algorithm is to hash the ID values in the first list against a Boolean. Then you travel on the second list to look up the ID values, and if the hash already contains a key of that ID, the ID is shared between the two lists.
You can also improve over the runtime of O(n^2) by sorting the lists by id, which is an O(n log n) operation, and traveling through the lists linearly to see if any of the values are shared. If you want to keep the order of the lists, you could create another, sorted copy of the lists but that would add memory space.
What you've got, I believe, is as good as you can do without changing the order of the list elements or creating a new data structure. I don't know your requirements, though.

How do i parse a map (foreach) in the same order i created it (JAVA)

So i have a map that i created (inserted data) in an order i wanted.
When parsing the map the 1st key returned in foreach is not the first key i inserted.
Is there a way for that to happen?
Also sorting my map is kinda tricky cause it has to be sorted by Value and in specific field within the Value.
Ty
Check out LinkedHashMap for a Map implementation with predictable iteration order. You also might consider just using a List if you're not actually doing lookup by keys.
Let's see. Your requirements seem to be:
You have a set of key / value pairs, where the keys are unique.
You want to be able to do fast lookup of the value for a given key.
You want to be able to iterate over the keys (or pairs) in insertion order.
You want to be able to iterate over the values in order of some field of the value type.
There is no single standard Java collection class that satisfies all of these requirements. And I don't think that Commons collections or Google collections would either ...
If you were to throw out requirement 3, then a TreeSet (instantiated with a custom Comparator) would do the job. If you were to throw out requirement 4, then a LinkedHashMap would do the job.
To satisfy all requirements you need to do one of the following:
Use a LinkedHashMap, and when you want to iterate in some order dependent on the values extract the map's values collection, sort it using your custom comparator, and
return an iterator for the sorted collection.
Use both a LinkedHashMap and a TreeMap, and update the two in parallel.
Create a custom fascade class for a LinkedHashMap and a TreeMap. This needs to keep both data structures up to date when you call put, remove etcetera, and also provide extra methods for getting the sorted values.
If you can sort your items up front by the value attribute, then you can use a LinkedListHashMap, since that preserves the order you specify. However, this seems a bit fragile, and is not suitable if you need to later add more items to the map.
The alternative is to store the values in a list, sorted as you need, and use binary search to retrieve items and find the insertion point for new items.
You can even wrap all this and put it behind a Map interface.
The Collections class provides binarySearch. Here's an outline:
Put your Value class in a list, List<Value> values.
Implement a Comparable<Value> class that compares values using the attribute you want to sort them on.
Use Comparator<Value> to sort the list.
Now that the list is sorted, you can use Collections.binarySearch(values, aValue, Comparator<Value>) to find the index of the actual value. Note that aValue isn't a real value - it's a value with the attributes set to provide the key, but the rest of it is uninitalized. The aValue is only used to hold the sort key.
In code
List<Value> values = new ArrayList<Values>();
// .. add values
values.add(new Value(key, data1, data2, etc..));
Comparator<Value> compValue = new Comparator<Value>() {
public int compare(Value v1, Value v2) {
return v1.getKey()>v2.getKey();
}
}
Collections.sort(values, compValue);
// now we can search on key
int index = Collections.binarySearch(values, new Value(keyTofind), valueComp);
Value foundValue = null; // value with the key may not be in the list
if (index>=0)
foundValue = values.get(index);
// we can also update the list
Value newValue = new Value(key, data, data2, etc...);
int insert = Collections.binarySearch(values, newValue, valueComp);
// insert will be negative
values.add((-insert)-1, newValue);
EDIT: If you wrap this up in a Map interface, e.g. extending AbstractMap, it will be serializable.

Categories

Resources