1

I want them to do a custom iterator, for a wrapper set.

ListIterator from(E elem)

Community
  • 1
  • 1
i30817
  • 1,356
  • 2
  • 13
  • 26
  • what do you mean by "descending from"? – Qnan Aug 18 '12 at 11:02
  • You could add the entrySet to an ArrayList (keeps insertion order) and iterate on a subList of that List. – assylias Aug 18 '12 at 11:03
  • descending iterator, from that value to the start. Entries in LinkedHashMap are bidirectional. – i30817 Aug 18 '12 at 11:03
  • there're many options, e.g. extract a list of items up to the one you'd like to start from, then iterate over it backwards – Qnan Aug 18 '12 at 11:06
  • Slows down the operation too much. Only want it using the underlying data, no copies. – i30817 Aug 18 '12 at 11:07
  • well, this question was here before, anyway, see http://stackoverflow.com/questions/7170871/iterating-through-a-linkedhashmap-in-reverse-order and http://stackoverflow.com/questions/8893231/how-to-traverse-linked-hash-map-in-reverse – Qnan Aug 18 '12 at 11:08
  • Those answers are unsatisfactory, one of the requires copies, the other doesn't even follow the requirement since TreeMap is not insert order. – i30817 Aug 18 '12 at 11:11
  • @i30817 The cost of a (shallow) copy, unless you have millions of elements, is probably negligible. – assylias Aug 18 '12 at 12:19
  • @assylias no, that's not acceptable. The whole reason for using a set is performance (i'm using the map wrapped in a set as you can see in the link). – i30817 Aug 18 '12 at 15:44

3 Answers3

0

Extend the implementation and add some façade methods

Dan
  • 1,030
  • 5
  • 12
  • And that's what i would do if the actual LinkedHashMap Entries subclass were not private – i30817 Aug 18 '12 at 11:00
  • Reflection will let you do that part http://tutorials.jenkov.com/java-reflection/private-fields-and-methods.html#fields – Dan Aug 18 '12 at 11:01
  • I just found something interesting. A 'package private' java construct will allow compilation if you put the package in the 'right' package even if it's not on the right jar (the ide doesn't autocomplete). Pity the actual needed class LinkedHashMap.Entry is private and the original interface Map.Entry is not birectional (or directional at all). – i30817 Aug 18 '12 at 12:00
  • I did some investigation and it might as well be copying stuff as using reflection. There are 4 fields affected which would probably slow down things. Now i'm wondering if i can just copy paste the damn source of LinkedHashMap into my project? My project is LGPL. – i30817 Aug 18 '12 at 15:42
0

I've got a solution now that doesn't use extension or reflection (or indeed LinkedHashMap)

What do you think?

package i3.util;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;

/**
 * This class is a like LinkedHashSet (insertion order) but it allows querying
 * the relative position of a element and has a ListIterator that can set and
 * insert anywhere.
 *
 * Warning: the iterator can change the order of the set by moving elements when
 * setting or adding. Elements that already exist are not ignored, but moved the
 * requested place. This changes iteration order
 *
 *
 * The iterators of this class are fail fast and will throw a
 * ConcurrentModificationException if their iterator are used with intervening
 * main class (or other iterators) mutative calls
 *
 * @author i30817 <i30817@gmail.com>
*/
public class LinkedSet<E> extends AbstractSet<E> {

//It holds the linked list
private Map<E, Node> m = new HashMap<E, Node>();
//head of that
protected Node head = new Node();
//this is copied to the map value in increments of iteratorAddStep on set.add
//(which only adds to the end, by insertion indexing)
private int monotonicallyIncreasing = 0;
//iterator add step may change when doing rebuilds of the 'space' between elements
//for the before/after functions on LinkedKeyIterator.add
private int iteratorAddStep = 10;
//for fail fast iterators
private int modCount;

/**
 * Start iterating from elem (inclusive)
 *
 *
 * @throws NoSuchElementException if E not part of the set
 * @param elem a element of the set
 * @return a ListIterator - doesn't support nextIndex() or previousIndex()
 */
public ListIterator<E> from(E elem) {
    Node e = m.get(elem);
    if (e == null) {
        throw new NoSuchElementException("the given element isn't part of the set");
    }
    return new LinkedKeyIterator(e);
}

@Override
public ListIterator<E> iterator() {
    return new LinkedKeyIterator();
}

/**
 * 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).relativeLocation;
    Integer highN = m.get(limitElem).relativeLocation;
    return targetN != null && highN != null && 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).relativeLocation;
    Integer low = m.get(previousElem).relativeLocation;
    return targetN != null && low != null && low < targetN;
}

@Override
public boolean add(E e) {
    if (!m.containsKey(e)) {
        Node n = new Node(e, monotonicallyIncreasing);
        monotonicallyIncreasing += iteratorAddStep;
        n.addBefore(head);//insertion order
        m.put(e, n);
        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) {
    Node n = m.remove(o);
    if (n != null) {
        n.remove();
        return true;
    }
    return false;
}

@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) {
    boolean all = true;
    for (Object e : c) {
        all &= m.containsKey(e);
    }
    return all;
}

@Override
public boolean retainAll(Collection<?> c) {
    boolean changed = false;
    Iterator<E> it = iterator();
    while (it.hasNext()) {
        E k = it.next();
        if (!c.contains(k)) {
            it.remove();
            changed = true;
        }
    }
    return changed;
}

@Override
public void clear() {
    modCount++;
    head.after = head.before = head;
    m.clear();
}

@Override
public String toString() {
    return  m.keySet().toString();
}

//linkedlist node class
protected final class Node {

    Node before, after;
    int relativeLocation;
    //needed for map removal during iteration
    E key;

    private void remove() {
        before.after = after;
        after.before = before;
        modCount++;
    }

    private void addBefore(Node existingEntry) {
        after = existingEntry;
        before = existingEntry.before;
        before.after = this;
        after.before = this;
        modCount++;
    }

    //head const
    public Node() {
        after = before = this;
        relativeLocation = 0;
    }

    public Node(E key, int value) {
        this.key = key;
        this.relativeLocation = value;
    }
}

protected class LinkedKeyIterator implements ListIterator<E> {

    Node nextEntry;
    Node lastReturned;
    int expectedModCount = modCount;

    public LinkedKeyIterator() {
        nextEntry = head.after;
    }

    public LinkedKeyIterator(Node startAt) {
        nextEntry = startAt;
    }

    public boolean hasPrevious() {
        return nextEntry.before != head;
    }

    public boolean hasNext() {
        return nextEntry != head;
    }

    public E next() {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (nextEntry == head) {
            throw new NoSuchElementException();
        }

        Node e = lastReturned = nextEntry;
        nextEntry = e.after;
        return e.key;
    }

    public E previous() {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (nextEntry.before == head) {
            throw new NoSuchElementException();
        }

        Node e = lastReturned = nextEntry.before;
        nextEntry = e;
        return e.key;
    }

    public void remove() {
        if (lastReturned == null) {
            throw new IllegalStateException();
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        m.remove(lastReturned.key);
        nextEntry = lastReturned.after;
        lastReturned.remove();
        lastReturned = null;
        expectedModCount = modCount;
    }

    @Override
    public void set(E e) {
        if (lastReturned == null) {
            throw new IllegalStateException();
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        if (lastReturned.key.equals(e)) {
            return;
        }
        //remove mapping for key since we are changing it
        m.remove(lastReturned.key);
        //put in the new one
        lastReturned.key = e;
        Node previousKeyOwner = m.put(e, lastReturned);
        if (previousKeyOwner != null) {
            //as it is a list mutation call, guard against stale iterator
            if(nextEntry == previousKeyOwner){
                nextEntry = nextEntry.after;
            }
            previousKeyOwner.remove();
        }
        //from m.remove and m.put, may help with 2 concurrent iterators on this instance
        //this method may not change modCount if previousKeyOwner is null
        expectedModCount = ++modCount;
    }

    @Override
    public void add(E e) {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        //calculate a good relative location, updating subsequent ones if needed
        int candidateLoc = nextEntry.before.relativeLocation + 1;
        //opsss, it's full
        if (candidateLoc == nextEntry.relativeLocation) {
            iteratorAddStep *= 1.6;
            for (Node current = nextEntry; current != head; current = current.after) {
                current.relativeLocation = current.relativeLocation + iteratorAddStep;
            }
        }

        Node n = m.get(e);
        if (n == null) {
            n = new Node(e, candidateLoc);
            m.put(e, n);
        } else {
            n.relativeLocation = candidateLoc;
            //as it is a list mutation call, guard against stale iterator
            if(nextEntry == n){
                nextEntry = nextEntry.after;
            }
            n.remove();
        }
        n.addBefore(nextEntry);
        expectedModCount = modCount;//add before changes modCount
    }

    @Override
    public int nextIndex() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public int previousIndex() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
}
i30817
  • 1,356
  • 2
  • 13
  • 26
0

If you want a custom iterator for a Map, do need to use a LinkedHashMap (which iterates in the same order as entries are added). Just use a HashMap and override the entrySet() method:

public class Map<K, V> extends HashMap<K, V> {

    public Set<K, V> entrySet() {
        return new HashSet<K, V>(super.entrySet()) {
            public Iterator<Map.Entry<K, V>> iterator () {
                 return // some custom implementation
            }
        };
    }

    // similar for keySet() if you wish
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722