Sorting by enum on adding to Set - java

I need to create priority set/array that bases on:
public interface IListener
{
public Priority getPriority();
public enum Priority
{
HIGHEST,
HIGH,
NORMAL,
LOW,
LOWEST;
}
}
IListeners are stored in:
HashMap<Class<? extends IListener>, Set<IListener>> listeners = new HashMap<>();
I am looking to make method that will always add IListener in 1st place after its Priority group.
Example:
Given Set contains some IListeners with this order.
{ HIGHEST, HIGHEST, HIGH, HIGH, LOW, LOW, LOW, LOWEST }
Adding listener with Priority == HIGH would result in:
{ HIGHEST, HIGHEST, HIGH, HIGH, HIGH, LOW, LOW, LOW, LOWEST }
Bold one being newly added.
I know I could just iterate and add at 1st "free slot", but question is rather - does Java provide some good-looking (maybe better?) solutions? Might be just for future reference.

As indicated in the comments, I don't think there is any collection in the JDK that exactly meets your requirements.
IListenerSet is an implementation of Set that meets your needs. The iterator always returns the elements in order of priority. If two elements have the same priority, they are returned in the order they were put into the set. The set supports addition and removal. The iterator supports the remove() method. The set cannot contain null, and throws a NullPointerException if you try to add null. The set cannot contain an IListener for which getPriority() returns null, and throws an IllegalArgumentException if you try to add such an element.
public final class IListenerSet<T extends IListener> extends AbstractSet<T> {
private final Map<IListener.Priority, Set<T>> map;
public IListenerSet() {
map = new EnumMap<>(IListener.Priority.class);
for (IListener.Priority p : IListener.Priority.values())
map.put(p, new LinkedHashSet<>());
}
public IListenerSet(Collection<? extends T> collection) {
this();
addAll(collection);
}
#Override
public int size() {
int size = 0;
for (Set<T> set : map.values())
size += set.size();
return size;
}
#Override
public boolean contains(Object o) {
if (!(o instanceof IListener))
return false;
IListener listener = (IListener) o;
IListener.Priority p = listener.getPriority();
return p != null && map.get(p).contains(listener);
}
#Override
public boolean add(T listener) {
IListener.Priority p = listener.getPriority();
if (p == null)
throw new IllegalArgumentException();
return map.get(p).add(listener);
}
#Override
public boolean remove(Object o) {
if (!(o instanceof IListener))
return false;
IListener listener = (IListener) o;
IListener.Priority p = listener.getPriority();
return p != null && map.get(p).remove(listener);
}
#Override
public void clear() {
for (Set<T> set : map.values())
set.clear();
}
#Override
public Iterator<T> iterator() {
return new Iterator<T>() {
private Iterator<T> iterator = map.get(IListener.Priority.values()[0]).iterator();
private int nextIndex = 1;
private Iterator<T> nextIterator = null;
#Override
public boolean hasNext() {
if (iterator.hasNext() || nextIterator != null)
return true;
IListener.Priority[] priorities = IListener.Priority.values();
while (nextIndex < priorities.length) {
Set<T> set = map.get(priorities[nextIndex++]);
if (!set.isEmpty()) {
nextIterator = set.iterator();
return true;
}
}
return false;
}
#Override
public T next() {
if (iterator.hasNext())
return iterator.next();
if (!hasNext())
throw new NoSuchElementException();
iterator = nextIterator;
nextIterator = null;
return iterator.next();
}
#Override
public void remove() {
iterator.remove();
}
};
}
}

An alternative approach is to use TreeSet with custom comparator and automatically assign autogenerated ids to the elements added to the set, so the latter elements always get bigger id which can be used in comparison:
public class IListenerSet extends AbstractSet<IListener> {
private long maxId = 0;
private final Map<IListener, Long> ids = new HashMap<>();
private final Set<IListener> set = new TreeSet<>(new Comparator<IListener>() {
#Override
public int compare(IListener o1, IListener o2) {
int res = o1.getPriority().compareTo(o2.getPriority());
if(res == 0)
res = ids.get(o1).compareTo(ids.get(o2));
return res;
}
});
#Override
public Iterator<IListener> iterator() {
return new Iterator<IListener>() {
Iterator<IListener> it = set.iterator();
private IListener e;
#Override
public boolean hasNext() {
return it.hasNext();
}
#Override
public IListener next() {
this.e = it.next();
return e;
}
#Override
public void remove() {
it.remove();
ids.remove(e);
}
};
}
#Override
public int size() {
return set.size();
}
#Override
public boolean contains(Object o) {
return ids.containsKey(o);
}
#Override
public boolean add(IListener e) {
if(ids.get(e) != null)
return false;
// assign new id and store it in the internal map
ids.put(e, ++maxId);
return set.add(e);
}
#Override
public boolean remove(Object o) {
if(!ids.containsKey(o)) return false;
set.remove(o);
return true;
}
#Override
public void clear() {
ids.clear();
set.clear();
}
}

Keep it easy:
You can combine several kinds of collections:
A LinkedHashSet allows you to store items by ordering them by insertion order (and with no repeated items).
A TreeMap allows you to map keys and values ordering them according to the keys.
Thus, you can declare this combination:
TreeMap<IListener.Priority, LinkedHashSet<IListener>> listenersByPriority=new TreeMap<IListener.Priority, LinkedHashSet<IListener>>(new PriorityComparator());
... and encapsulate it in a proper abstraction to manage it:
public class ListenerManager
{
private final TreeMap<IListener.Priority, LinkedHashSet<IListener>> listenersByPriority=new TreeMap<IListener.Priority, LinkedHashSet<IListener>>();
private int size;
public void addListener(IListener listener)
{
synchronized (listenersByPriority)
{
LinkedHashSet<IListener> list=listenersByPriority.get(listener.getPriority());
if (list == null)
{
list=new LinkedHashSet<IListener>();
listenersByPriority.put(listener.getPriority(), list);
}
list.add(listener);
size++;
}
}
public Iterator<IListener> iterator()
{
synchronized (listenersByPriority)
{
List<IListener> output=new ArrayList<IListener>(size);
for (LinkedHashSet<IListener> list : listenersByPriority.values())
{
for (IListener listener : list)
{
output.add(listener);
}
}
return output.iterator();
}
}
}
When declaring a TreeMap, it is usually necessary an specific implementation of Comparator coupled to the key class, but it is not necessary in this case, because enums are already comparable by its ordinal. (thanks to Paul Boddington). And the ordinal of each enum item is the position it is placed in the declaration.

Related

Set that uniquely contains a key but ordered on different field

I'm looking for a Java collection, possibly in standard library, which is able to collect the following structure:
class Item {
String key;
double score;
}
And with the following properties:
Only one Item with the same key is allowed (like a set)
insert, remove, check existance in max O(logn)
traversal ordered by score, finding next in max O(logn)
As far as I understood standard OrderedSet must have comparable interface coherent with equals() interface, but that is not my case as two items with different key may have the same score.
In fact I've notice that TreeSet uses the comparator returning 0 to check if the item is already present.
Any suggestion?
Now that insert has been relaxed to O(log n), you can do this with a double-set, i.e. implement your own set that maintains 2 sets behind the scenes.
Best would be if you can modify class Item to implement equals() and hashCode() to only use the key field. In that case your class would use a HashSet and a TreeSet. If hashCode() covers more than just the key field, then use two TreeSet objects.
final class ItemSet implements NavigableSet<Item> {
private final Set<Item> keySet = new HashSet<>();
// or: new TreeSet<>(Comparator.comparing(Item::getKey));
private final TreeSet<Item> navSet = new TreeSet<>(Comparator.comparingDouble(Item::getScore)
.thenComparing(Item::getKey));
//
// Methods delegating to keySet for unique key access and for unordered access
//
#Override public boolean contains(Object o) { return this.keySet.contains(o); }
#Override public boolean containsAll(Collection<?> c) { return this.keySet.containsAll(c); }
#Override public int size() { return this.keySet.size(); }
#Override public boolean isEmpty() { return this.keySet.isEmpty(); }
//
// Methods delegating to navSet for ordered access
//
#Override public Comparator<? super Item> comparator() { return this.navSet.comparator(); }
#Override public Object[] toArray() { return this.navSet.toArray(); }
#Override public <T> T[] toArray(T[] a) { return this.navSet.toArray(a); }
#Override public Item first() { return this.navSet.first(); }
#Override public Item last() { return this.navSet.last(); }
#Override public Item lower(Item e) { return this.navSet.lower(e); }
#Override public Item floor(Item e) { return this.navSet.floor(e); }
#Override public Item ceiling(Item e) { return this.navSet.ceiling(e); }
#Override public Item higher(Item e) { return this.navSet.higher(e); }
//
// Methods delegating to both keySet and navSet for mutation of this set
//
private final class ItemSetIterator implements Iterator<Item> {
private final Iterator<Item> iterator = ItemSet.this.navSet.iterator();
private Item keyToRemove;
#Override
public boolean hasNext() {
return iterator.hasNext();
}
#Override
public Item next() {
keyToRemove = iterator.next();
return keyToRemove;
}
#Override
public void remove() {
iterator.remove();
ItemSet.this.keySet.remove(keyToRemove);
keyToRemove = null;
}
}
#Override
public Iterator<Item> iterator() {
return new ItemSetIterator();
}
#Override
public void clear() {
this.keySet.clear();
this.navSet.clear();
}
#Override
public boolean add(Item e) {
if (! this.keySet.add(e))
return false; // item already in set
if (! this.navSet.add(e))
throw new IllegalStateException("Internal state is corrupt");
return true;
}
#Override
public boolean remove(Object o) {
if (! this.keySet.remove(o))
return false; // item not in set
if (! this.navSet.remove(o))
throw new IllegalStateException("Internal state is corrupt");
return true;
}
#Override
public boolean addAll(Collection<? extends Item> c) {
boolean changed = false;
for (Item item : c)
if (add(item))
changed = true;
return changed;
}
#Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for (Object o : c)
if (remove(o))
changed = true;
return changed;
}
#Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public Item pollFirst() {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public Item pollLast() {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public NavigableSet<Item> descendingSet() {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public Iterator<Item> descendingIterator() {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public SortedSet<Item> headSet(Item toElement) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public NavigableSet<Item> headSet(Item toElement, boolean inclusive) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public SortedSet<Item> tailSet(Item fromElement) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public NavigableSet<Item> tailSet(Item fromElement, boolean inclusive) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public SortedSet<Item> subSet(Item fromElement, Item toElement) {
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public NavigableSet<Item> subSet(Item fromElement, boolean fromInclusive, Item toElement, boolean toInclusive) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
I don't think such a structure exists. You didn't specify traversal performance requirements, so you could use a normal Set and add the values to a list and sort that list by score for traversal.
A HashSet does not guarantee any order of its elements. If you need this guarantee, consider using a TreeSet to hold your elements
but for achieving the unique by key and maintain constant time override hashCode() and equals() effectively what you looking for as the below :
class Item {
String key;
double score;
public Item(String key, double score) {
this.key = key;
this.score = score;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
return key.equals(item.key);
}
#Override
public int hashCode() {
return Objects.hash(key);
}
#Override
public String toString() {
return "Item{" +
"key='" + key + '\'' +
", score=" + score +
'}';
}
}
// main
public static void main(String[] args) {
Set<Item> itemSet = new HashSet<>();
itemSet.add(new Item("1", 1));
itemSet.add(new Item("1", 2));
itemSet.add(new Item("2", 1));
//to get a sorted TreeSet
//Add all your objects to the TreeSet, you will get a sorted Set.
//TreeSet myTreeSet = new TreeSet();
//myTreeSet.addAll(itemSet);
//System.out.println(myTreeSet);
}
output :
Item{key='1', score=1.0}
Item{key='2', score=1.0}
Thanks to people that made me think with their comments and answers. I believe we can achieve the requirements by using:
TreeMap<Double, HashSet<Item>>
Just because (I haven't said that) two equal keys yield the same score; but more in general it's enough to have two maps of set: one (ordered) with ordering field as key, and one (not ordered) with unique field as key.
Only one Item with the same key is allowed (like a set)
Your Item class should implement hashCode() and equals() using just the key attribute.
insert, remove, check existance in constant time
TreeSet add() and remove() are O(ln N), so they do not meet your criteria.
HashSet add() and remove() usually are O(1).
traversal ordered by score
What are your performance requirements here? How frequently will you be traversing the collection? If you will be mainly adding and removing the items and rarely traversing it, then you can make a copy of a HashSet to a TreeSet during the traversing operation.

Hashmap with a .class object as a key

I've created a hashmap with .class objects for keys.
Hashmap<Class<? extends MyObject>, Object> mapping = new Hashmap<Class<? extends MyObject>, Object>();
This is all well and fine, but I'm getting strange behaviour that I can only attribute to strangeness with the hash function. Randomly during runtime, iterating through the hashmap will not hit every value; it will miss one or two. I think this may be due to the .class object not being final, and therefore it changes causing it to map to a different hash value. With a different hash value, the hashmap wouldn't be able to correctly correlate the key with the value, thus making it appear to have lost the value.
Am I correct that this is what is going on? How can I work around this? Is there a better way to accomplish this form of data structure?
Edit: I really thought I was onto something with the hash function thing, but I'll post my real code to try and figure this out. It may be a problem with my implementation of a multimap. I've been using it for quite some time and haven't noticed any issues until recently.
/**
* My own implementation of a map that maps to a List. If the key is not present, then
* the map adds a List with a single entry. Every subsequent addition to the key
* is appended to the List.
* #author
*
* #param <T> Key
* #param <K> Value
*/
public class MultiMap<T, K> implements Map<T, List<K>>, Serializable, Iterable<K> {
/**
*
*/
private static final long serialVersionUID = 5789101682525659411L;
protected HashMap<T, List<K>> set = new HashMap<T, List<K>>();
#Override
public void clear() {
set = new HashMap<T, List<K>>();
}
#Override
public boolean containsKey(Object arg0) {
return set.containsKey(arg0);
}
#Override
public boolean containsValue(Object arg0) {
boolean output = false;
for(Iterator<List<K>> iter = set.values().iterator();iter.hasNext();) {
List<K> searchColl = iter.next();
for(Iterator<K> iter2 = searchColl.iterator(); iter2.hasNext();) {
K value = iter2.next();
if(value == arg0) {
output = true;
break;
}
}
}
return output;
}
#Override
public Set<Entry<T, List<K>>> entrySet() {
Set<Entry<T, List<K>>> output = new HashSet<Entry<T,List<K>>>();
for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) {
T key = iter1.next();
for(Iterator<K> iter2 = set.get(key).iterator(); iter2.hasNext();) {
K value = iter2.next();
List<K> input = new ArrayList<K>();
input.add(value);
output.add(new AbstractMap.SimpleEntry<T,List<K>>(key, input));
}
}
return output;
}
#Override
public boolean isEmpty() {
return set.isEmpty();
}
#Override
public Set<T> keySet() {
return set.keySet();
}
#Override
public int size() {
return set.size();
}
#Override
public Collection<List<K>> values() {
Collection<List<K>> values = new ArrayList<List<K>>();
for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) {
T key = iter1.next();
values.add(set.get(key));
}
return values;
}
#Override
public List<K> get(Object key) {
return set.get(key);
}
#Override
public List<K> put(T key, List<K> value) {
return set.put(key, value);
}
public void putValue(T key, K value) {
if(set.containsKey(key)) {
set.get(key).add(value);
}
else {
List<K> setval = new ArrayList<K>();
setval.add(value);
set.put(key, setval);
}
}
#Override
public List<K> remove(Object key) {
return set.remove(key);
}
public K removeValue(Object value) {
K valueRemoved = null;
for(T key:this.keySet()) {
for(K val:this.get(key)) {
if(val.equals(value)) {
List<K> temp = this.get(key);
temp.remove(value);
valueRemoved = val;
this.put(key, temp);
}
}
}
return valueRemoved;
}
#Override
public void putAll(Map<? extends T, ? extends List<K>> m) {
for(Iterator<? extends T> iter = m.keySet().iterator(); iter.hasNext();) {
T key = iter.next();
set.put(key, m.get(key));
}
}
#Override
public Iterator<K> iterator() {
return new MultiMapIterator<K>(this);
}
}
Perhaps there is an issue with my iterator? I'll post that code as well.
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MultiMapIterator<T> implements Iterator<T> {
private MultiMap <?, T> map;
private Iterator<List<T>> HashIter;
private Iterator<T> govIter;
private T value;
public MultiMapIterator(MultiMap<?, T> map) {
this.map = map;
HashIter = map.values().iterator();
if(HashIter.hasNext()) {
govIter = HashIter.next().iterator();
}
if(govIter.hasNext()) {
value = govIter.next();
}
}
#Override
public boolean hasNext() {
if (govIter.hasNext()) {
return true;
}
else if(HashIter.hasNext()) {
govIter = HashIter.next().iterator();
return this.hasNext();
}
else {
return false;
}
}
#Override
public T next() {
if(!this.hasNext()) {
throw new NoSuchElementException();
}
else {
value = govIter.next();
return value;
}
}
#Override
public void remove() {
map.remove(value);
}
}
Sorry for the long tracts of code. Thank you for spending time helping me with this.
You pull the a value out of govIter in the constructor, but never return it.
Your iterator remove method is completely wrong. You are iterating values, but calling the map.remove which removes by key. you simply want to call govIter.remove() (unless you need to avoid empty lists, in which case it's more complicated).
Your hasNext() method could also have problems depending on whether or not you allow empty Lists values in your multimap.

Java ListSet somewhere?

Looking for a insertion order collection that also allows efficient querying and subset views of positions (like sublist). Seems the most straightforward option for this would be to take the linked list approach of List, embed the nodes as map values and expose part or all of the list interface on the class.
Would someone bitch to Oracle about this? Having NavigableMap/Set added for sorted maps and sets and not having the far more common insertion order equivalent...
edit: please don't suggest LinkedHashSet - it doesn't have any way to query the position or to do a relative subset.
you mean like java.util.LinkedHashSet:
Hash table and linked list implementation of the Set interface, with
predictable iteration order. This implementation differs from HashSet
in that it maintains a doubly-linked list running through all of its
entries. This linked list defines the iteration ordering, which is the
order in which elements were inserted into the set (insertion-order).
Note that insertion order is not affected if an element is re-inserted
into the set. (An element e is reinserted into a set s if s.add(e) is
invoked when s.contains(e) would return true immediately prior to the
invocation.)
edit2: New final version
Here is a version only for sets with slightly adjusted function (divided into two, no longer accepts null as 'beginning of the map') that probably has less bugs
public class LinkedSet<E> implements Set<E> {
private LinkedHashMap<E, Integer> m = new LinkedHashMap<E, Integer>();
private int monoticallyIncreasing;
/**
* Returns true if the value target was added before (exclusive)
* limitElem in insertion order.
*
* If target or limit are not present on the set this method returns false
*
* #param limitElem a E that may be a element of the set or not.
* #return if target was added before limit (can be reset by removing and
* re-adding the target, that changes iteration order).
*/
public boolean containsBefore(E target, E limitElem) {
if (isEmpty()) {
return false;
}
Integer targetN = m.get(target);
if (targetN == null) {
return false;
}
Integer highN = m.get(limitElem);
if (highN == null) {
return false;
}
return targetN < highN;
}
/**
* Returns true if the value target was added after (exclusive)
* previousElem in insertion order.
*
* If target or previous are not present on the set this method returns false
*
* #param previousElem a E that may be a element of the set or not.
* #return if target was added before previous (can be reset by removing and
* re-adding the target, that changes iteration order).
*/
public boolean containsAfter(E target, E previousElem) {
if (isEmpty()) {
return false;
}
Integer targetN = m.get(target);
if (targetN == null) {
return false;
}
Integer low = m.get(previousElem);
if (low == null) {
return false;
}
return low < targetN;
}
#Override
public boolean add(E e) {
Integer pos = m.get(e);
if (pos == null) {
m.put(e, monoticallyIncreasing++);
return true;
}
return false;
}
#Override
public int size() {
return m.size();
}
#Override
public boolean isEmpty() {
return m.isEmpty();
}
#Override
public boolean contains(Object o) {
return m.containsKey(o);
}
#Override
public Object[] toArray() {
Object[] result = new Object[size()];
int i = 0;
for (E e : this) {
result[i++] = e;
}
return result;
}
#Override
#SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size) {
a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
}
int i = 0;
Object[] result = a;
for (E e : this) {
result[i++] = e;
}
if (a.length > size) {
//peculiar toArray contract where it doesn't care about the rest
a[size] = null;
}
return a;
}
#Override
public boolean remove(Object o) {
return m.remove(o) != null;
}
#Override
public boolean addAll(Collection<? extends E> c) {
boolean changed = false;
for (E e : c) {
changed |= add(e);
}
return changed;
}
#Override
public boolean containsAll(Collection<?> c) {
return m.keySet().containsAll(c);
}
#Override
public boolean retainAll(Collection<?> c) {
return m.keySet().retainAll(c);
}
#Override
public boolean removeAll(Collection<?> c) {
return m.keySet().removeAll(c);
}
#Override
public void clear() {
m.clear();
}
#Override
public Iterator<E> iterator() {
return m.keySet().iterator();
}
}

Problems with add/remove/equals/string-method in MultiSet-class

This is my class:
public class MultiSet<E> extends AbstractCollection<E>
{
private int size = 0;
private Map<E, Integer> values = new HashMap<E, Integer>();
public MultiSet()
{
}
public MultiSet(Collection<E> c)
{
addAll(c);
}
#Override
public boolean add(E o)
{
throw new UnsupportedOperationException();
}
#Override
public boolean remove(Object o)
{
throw new UnsupportedOperationException();
}
public Iterator<E> iterator()
{
return new Iterator<E>()
{
private Iterator<E> iterator = values.keySet().iterator();
private int remaining = 0;
private E current = null;
public boolean hasNext()
{
return remaining > 0 || iterator.hasNext();
}
public E next()
{
if (remaining == 0)
{
remaining = values.get(current);
}
remaining--;
return current;
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
public boolean equals(Object object)
{
if (this == object) return true;
if (this == null) return false;
if (this.getClass() != object.getClass()) return false;
MultiSet<E> o = (MultiSet<E>) object;
return o.values.equals(values);
}
public int hashCode()
{
return values.hashCode()*163 + new Integer(size).hashCode()*389;
}
public String toString()
{
String res = "";
for (E e : values.keySet());
//res = ???;
return getClass().getName() + res;
}
public int size()
{
return size;
}
}
So basically, i need to implement my add/remove-methods correctly, to add or remove elements to/from the Set.
To me, it seems like my equals is correct, but Eclipse says that in the line:
MultiSet<E> o = (MultiSet<E>) object;
there is an unchecked cast from object to Multiset<E>
Any thoughts?
Also, in my toString method, i'm not 100% sure how to define "res"?
Thanks,
// Chris
use this instead:
MultiSet<?> o = (MultiSet<?>) object;
this is necessary due to how generics are implemented in java.

How to obtain index of a given LinkedHashSet element without iteration?

Is it even possible?
Say you have
private Set<String> names = new LinkedHashSet<String>();
and Strings are "Mike", "John", "Karen".
Is it possible to get "1" in return to "what's the index of "John" without iteration?
The following works fine .. with this question i wonder if there is a better way
for (String s : names) {
++i;
if (s.equals(someRandomInputString)) {
break;
}
}
The Set interface doesn't have something like as an indexOf() method. You'd really need to iterate over it or to use the List interface instead which offers an indexOf() method.
If you would like to, converting Set to List is pretty trivial, it should be a matter of passing the Set through the constructor of the List implementation. E.g.
List<String> nameList = new ArrayList<String>(nameSet);
// ...
I don't believe so, but you could create a LinkedHashSetWithIndex wrapper class that would do the iteration for you, or keep a separate table with the indexes of each entry, if the performance decrease is acceptable for your use case.
It is generally not possible for a Set to return the index because it's not necessarily well defined for the particular Set implementation. For example it says in the HashSet documentation
It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.
So you shouldn't say the type is Set when what you actually expect is a Set implementing som order.
Here is an implementation that does insertions, removals, retainings, backed by an arraylist to achieve o(1) on get(index).
/**
* #Author Mo. Joseph
*
* Allows you to call get with o(1) instead of o(n) to get an instance by index
*/
public static final class $IndexLinkedHashSet<E> extends LinkedHashSet<E> {
private final ArrayList<E> list = new ArrayList<>();
public $IndexLinkedHashSet(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
}
public $IndexLinkedHashSet() {
super();
}
public $IndexLinkedHashSet(int initialCapacity) {
super(initialCapacity);
}
public $IndexLinkedHashSet(Collection<? extends E> c) {
super(c);
}
#Override
public synchronized boolean add(E e) {
if ( super.add(e) ) {
return list.add(e);
}
return false;
}
#Override
public synchronized boolean remove(Object o) {
if ( super.remove(o) ) {
return list.remove(o);
}
return false;
}
#Override
public synchronized void clear() {
super.clear();
list.clear();
}
public synchronized E get(int index) {
return list.get(index);
}
#Override
public synchronized boolean removeAll(Collection<?> c) {
if ( super.removeAll(c) ) {
return list.removeAll(c);
}
return true;
}
#Override
public synchronized boolean retainAll(Collection<?> c) {
if ( super.retainAll(c) ) {
return list.retainAll(c);
}
return false;
}
/**
* Copied from super class
*/
#Override
public synchronized boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
}
To test it:
public static void main(String[] args) {
$IndexLinkedHashSet<String> abc = new $IndexLinkedHashSet<String>();
abc.add("8");
abc.add("8");
abc.add("8");
abc.add("2");
abc.add("3");
abc.add("4");
abc.add("1");
abc.add("5");
abc.add("8");
System.out.println("Size: " + abc.size());
int i = 0;
while ( i < abc.size()) {
System.out.println( abc.get(i) );
i++;
}
abc.remove("8");
abc.remove("5");
System.out.println("Size: " + abc.size());
i = 0;
while ( i < abc.size()) {
System.out.println( abc.get(i) );
i++;
}
abc.clear();
System.out.println("Size: " + abc.size());
i = 0;
while ( i < abc.size()) {
System.out.println( abc.get(i) );
i++;
}
}
Which outputs:
Size: 6
8
2
3
4
1
5
Size: 4
2
3
4
1
Size: 0
Ofcourse remove, removeAll, retainAll now has the same or worse performance as ArrayList. But I do not use them and so I am ok with that.
Enjoy!
EDIT:
Here is another implementation, which does not extend LinkedHashSet because that's redundant. Instead it uses a HashSet and an ArrayList.
/**
* #Author Mo. Joseph
*
* Allows you to call get with o(1) instead of o(n) to get an instance by index
*/
public static final class $IndexLinkedHashSet<E> implements Set<E> {
private final ArrayList<E> list = new ArrayList<>( );
private final HashSet<E> set = new HashSet<> ( );
public synchronized boolean add(E e) {
if ( set.add(e) ) {
return list.add(e);
}
return false;
}
public synchronized boolean remove(Object o) {
if ( set.remove(o) ) {
return list.remove(o);
}
return false;
}
#Override
public boolean containsAll(Collection<?> c) {
return set.containsAll(c);
}
public synchronized void clear() {
set.clear();
list.clear();
}
public synchronized E get(int index) {
return list.get(index);
}
public synchronized boolean removeAll(Collection<?> c) {
if ( set.removeAll(c) ) {
return list.removeAll(c);
}
return true;
}
public synchronized boolean retainAll(Collection<?> c) {
if ( set.retainAll(c) ) {
return list.retainAll(c);
}
return false;
}
public synchronized boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
#Override
public synchronized int size() {
return set.size();
}
#Override
public synchronized boolean isEmpty() {
return set.isEmpty();
}
#Override
public synchronized boolean contains(Object o) {
return set.contains(o);
}
#Override
public synchronized Iterator<E> iterator() {
return list.iterator();
}
#Override
public synchronized Object[] toArray() {
return list.toArray();
}
#Override
public synchronized <T> T[] toArray(T[] a) {
return list.toArray(a);
}
}
Now you have two implementations, I would prefer the second one.
Although not as efficient for the machine, this achieves it in one line:
int index = new ArrayList<String>(names).indexOf("John");
A better way there is not, only a single lined one (which makes use of the iterator, too but implicitly):
new ArrayList(names).get(0)
You can convert your Set to List then you can do any indexing operations.
Example: need to crop Set list to 5 items.
Set<String> listAsLinkedHashSet = new LinkedHashSet<>();
listAsLinkedHashSet.add("1");
listAsLinkedHashSet.add("2");
listAsLinkedHashSet.add("3");
listAsLinkedHashSet.add("4");
listAsLinkedHashSet.add("1");
listAsLinkedHashSet.add("2");
listAsLinkedHashSet.add("5");
listAsLinkedHashSet.add("7");
listAsLinkedHashSet.add("9");
listAsLinkedHashSet.add("8");
listAsLinkedHashSet.add("1");
listAsLinkedHashSet.add("10");
listAsLinkedHashSet.add("11");
List<String> listAsArrayList = new ArrayList<>(listAsLinkedHashSet);
//crop list to 5 elements
if (listAsArrayList.size() > 5) {
for (int i = 5; i < listAsArrayList.size(); i++) {
listAsArrayList.remove(i);
i--;
}
}
listAsLinkedHashSet.clear();
listAsLinkedHashSet.addAll(listAsArrayList);

Categories