Идиома для парной итерации через отсортированную коллекцию

Есть ли идиома Java для парной итерации через элементы отсортированного Collection? Под этим я подразумеваю, что каждая итерация имеет доступ к одному элементу коллекции и к следующему элементу коллекции?

Для отсортированных List (и массивов) это можно сделать, используя индекс в коллекции:

 final int n = list.size();
 assert 2 <= n;
 for (int i = 0; i < n - 1; ++i) {
    final Thing thing1 = list.get(i);
    final Thing thing2 = list.get(i+1);
    operateOnAdjacentPair(thing1, thing2);
 }

Но как насчет SortedSet? (для SortedMap вы можете использовать его entrySet(), что эквивалентно случаю SortedSet).


Итак, например, если ваш отсортированный набор содержал значения {1, 2, 3, 4}, итерации были бы для пар (1, 2), (2, 3), (3, 4), в этом порядке.

Ответы

Ответ 1

Вы можете просто реализовать его следующим образом (и применить аналогичную стратегию к другим коллекциям):

Iterator<Thing> iter = set.iterator();
Thing previous = iter.hasNext() ? iter.next() : null;
while (iter.hasNext()) {
    final Thing current = iter.next();
    operateOnAdjacentPair(previous, current);
    previous = current;
}

Ответ 2

Iterator<Thing> thingerator = coll.iterator();
if (thingerator.hasNext()) {
    Thing thing1 = thingerator.next();
    while (thingerator.hasNext()) {
      final Thing thing2 = thingerator.next();
      doStuffToThings(thing1, thing2);

      thing1 = thing2;
    }
}

Ответ 3

Напишите реализацию Iterator, например. (просто сбрасывая верхнюю часть головы, поэтому код может не работать как есть)

public class PairwiseIterator<T> implements Iterator<List<T>> {
    private final Iterator<T> elements;
    private T last;

    public PairwiseIterator(Collection<T> elements) {
        this.elements = elements.iterator();
        last = elements.hasNext() ? elements.next() : null;
    }

    @Override
    public boolean hasNext() {
        return elements.hasNext();
    }

    @Override
    public List<T> next() {
        List<T> result = ImmutableList.of(last, elements.next());
        last = result.get(1);
        return result;
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Remove not allowed with this iterator");
    }

    public static <U> Iterable<List<U>> iterable(final Collection<U> elements) {
        return new Iterable() {
            public Iterator<U> iterator() {
                return new PairwiseIterator(elements);
            }
        }
    }
}

У меня, вероятно, не все типы правильные, но метод "итерабельный" упрощает использование в конструкциях foreach:

for(List<String> pair : PairwiseIterator.iterable(orderedSetOfStrings)) {
    // ... do what you need to ...
}

Ответ 4

Для Set (и других неиндексируемых коллекций) вам нужно будет использовать их Iterator, которые возвращаются iterator() метод Collection:

Iterator<Thing> iter = set.iterator();
Thing thing1 = iter.next();  // might want to check if this exists
while (iter.hasNext()) {
    Thing thing2 = iter.next();
    operateOnAdjacentPair(thing1, thing2);
    thing1 = thing2;
}

Вы можете сделать то же самое для Map s, используя Iterator своих entrySet() s.


Теперь, когда я лучше понимаю ваш вопрос, вы также можете попробовать следующее:

Iterator<Thing> iter1 = set.iterator(), iter2 = set.iterator();

if (iter2.hasNext())
    iter2.next();  // burn first element

while (iter2.hasNext()) {
    final Thing thing1 = iter1.next();
    final Thing thing2 = iter2.next();
    operateOnAdjacentPair(thing1, thing2);
}

Ответ 5

Guava предлагает PeekingIterator, который делает итерацию проще и безопаснее:

PeekingIterator<Thing> iter = 
    Iterators.peekingIterator(set.iterator());
while (iter.hasNext()) {
    final Thing thing1 = iter.next();
   if (iter.hasNext()) { // don't forget this one
        operateOnAdjacentPair(thing1, iter.peek());
    }
}

Когда код цикла становится более сложным, выгода может быть более очевидной.