Предоставить итератор для содержимого двух списков одновременно? - PullRequest
27 голосов
/ 29 июня 2010

Предположим, у меня есть это:

public class Unit<MobileSuit, Pilot> {

    ...

    List<MobileSuit> mobileSuits;
    List<Pilot> pilots;

    ...
}

И я бы хотел перебрать пару каждого из них самым простым способом вне этого класса.Как мне это сделать?Я думал о том, чтобы сделать это:

public class Unit<MobileSuit, Pilot> {

    ...
    Iterator<MobileSuit> iteratinMechas;
    Iterator<Pilot> iteratinPeople;

    class IteratorCustom<MobileSuit, Pilot> implements Iterator {

        public boolean hasNext() {
            return iteratinMechas.hasNext() && iteratinPeople.hasNext();
        }

        public void remove() {
            iteratinMechas.remove();
            iteratinPeople.remove();
        }

        public Object next() {
            // /!\
        }

    }

    public Iterator iterator() {
        return new IteratorCustom<MobileSuit, Pilot>(mobileSuits, pilots);
    }
}

Что-то в этом роде.

В любом случае, проблема в том, что я не могу действительно вернуть только один объект из next (), и я такжеИтератор не может принимать более одного типа.Итак, есть мысли?

Кроме того, я не могу создать новый класс для объединения MobileSuit и Pilot.Мне нужно держать их отдельно, хотя я перебираю оба одновременно.Причина в том, что могут быть мобильные костюмы, в которых нет пилотов, и я не уверен, как это исправить, сохранив их в одном классе.Этот класс должен быть обработан в других местах, поэтому я должен был бы объединить интерфейс вокруг этого и многих других вещей.Предположим, что MobileSuit и Pilot должны быть разделены.

Ответы [ 11 ]

12 голосов
/ 29 июня 2010

В любом случае, проблема в том, что я не могу вернуть только один объект из next (), и у меня также не может быть, чтобы Iterator принимал более одного типа.Итак, есть мысли?

Очевидно, вам понадобится легкий класс "пары".Это примерно аналогично внутреннему классу Map.Entry.

Вот примерное решение для универсального решения:

public class ParallelIterator <T1, T2> implements Iterator<Pair<T1, T2>> {

    public class Pair<TT1, TT2> {
        private final TT1 v1;
        private final TT2 v2;
        private Pair(TT1 v1, TT2 v2) { this.v1 = v1; this.v2 = v2; }
        ...
    }

    private final Iterator<T1> it1;
    private final Iterator<T2> it2;

    public ParallelIterator(Iterator<T1> it1, Iterator<T2> it2) { 
        this.it1 = it1; this.it2 = it2;
    }

    public boolean hasNext() { return it1.hasNext() && it2.hasNext(); }

    public Pair<T1, T2> next() {
        return new Pair<T1, T2>(it1.next(), it2.next());
    }

    ...

}

Примечание: это явно не касается случаев, когда списки имеютразной длины.Что произойдет, так это то, что дополнительные элементы в конце длинного списка будут игнорироваться.

5 голосов
/ 12 ноября 2011

Это скопировано + отредактировано из ответа Стивена С.Не стесняйтесь использовать:

public class Pair<T1, T2> {
    private final T1 v1;
    private final T2 v2;
    Pair(T1 v1, T2 v2) {
        this.v1 = v1;
        this.v2 = v2;
    }
    public T1 first(){
        return v1;
    }
    public T2 second(){
        return v2;
    }
}

public class ParallelIterator <T1, T2> implements Iterator<Pair<T1, T2>> {

    private final Iterator<T1> it1;
    private final Iterator<T2> it2;

    public ParallelIterator(Iterator<T1> it1, Iterator<T2> it2) { 
        this.it1 = it1; this.it2 = it2;
    }

    @Override
    public boolean hasNext() { return it1.hasNext() && it2.hasNext(); }

    @Override
    public Pair<T1, T2> next() {
        return new Pair<T1, T2>(it1.next(), it2.next());
    }

    @Override
    public void remove(){
        it1.remove();
        it2.remove();
    }
}

public class IterablePair <T1, T2> implements Iterable<Pair<T1,T2>> {
    private final List<T1> first;
    private final List<T2> second;

    public IterablePair(List<T1> first, List<T2> second) { 
        this.first = first;
        this.second = second;
    }

    @Override
    public Iterator<Pair<T1, T2>> iterator(){
        return new ParallelIterator<T1,T2>( first.iterator(), second.iterator() );
    }
}

void someFunction(){
    IterablePair<X,Y> listPair = new IterablePair<X,Y>( x, y );
    for( Pair<X,Y> pair : listPair ){
        X x = pair.first();
        ...
    }
}

Это останавливается, как только в любом списке нет элементов, поэтому вы можете захотеть проверить списки одинакового размера перед созданием IterablePair.

4 голосов
/ 29 июня 2010

Кроме того, я не могу создать новый класс для объединения MobileSuit и Pilot.

Это не звучит правильно.Похоже, вы не можете заменить MobileSuit и Pilot одним классом, но я не вижу причин, почему у вас не может быть одного класса, который объединяет их - т.е.тот, который просто имеет метод getPilot() и метод getMobileSuit().Вы можете использовать универсальный класс Pair для той же цели, но пользовательский класс будет проще использовать.

С другой стороны, если вы хотите выполнить такую ​​операцию «архивирования» в нескольких местах, это может быть одним из решений.В качестве альтернативы, вы могли бы написать универсальный интерфейс для представления процесса объединения двух разных элементов, которые могли бы возвращать SuitedPilot или любой другой класс комбинации.

2 голосов
/ 29 июня 2010

Причина в том, что могут быть мобильные костюмы, в которых нет пилотов, и я не уверен, как это исправить, сохранив их в одном классе.

Вы можете использоватьнулевые значения, верно?Какой правильный способ сделать это - пусть каждый костюм отслеживает своего пилота.Если у него нет пилот-сигнала, укажите это с нулевым значением там.

Но, если вы мертвы, не делайте этого по какой-то причине ...

public class SuitAndPilot
{
    public MobileSuit suit;
    public Pilot pilot;

    public SuitAndPilot(Suit s, Pilot p) {
           suit = s;
           pilot = p;
    }
}
1 голос
/ 24 февраля 2017

По сути, предположим, что MobileSuit и Pilot должны быть разделены.

Это нормально, но здесь вы пытаетесь рассматривать их как единое целое, поэтому структурируйте свой код таким образом. В приведенных выше предложениях используется класс Pair или Map.Entry, но гораздо лучше предоставить объект с четким именем, представляющий MobileSuit с Pilot, например ::

public class OccupiedSuit {
  private final MobileSuit suit;
  private final Pilot pilot;

  public OccupiedSuit(MobileSuit suit, Pilot pilot) {
    this.suit = checkNotNull(suit);
    this.pilot = checkNotNull(pilot);
  }

  // getters, equals, hashCode, toString
  // or just use @AutoValue: https://github.com/google/auto/tree/master/value
}

Затем, вместо создания пользовательского Iterator / Iterable, просто напишите вспомогательную функцию, которая объединяет два списка . Например:

public static List<OccupiedSuit> assignPilots(
    Iterable<MobileSuit> suits, Iterable<Pilot> pilots) {
  Iterator<MobileSuit> suitsIter = suits.iterator();
  Iterator<Pilot> pilotsIter = pilots.iterator();
  ImmutableList.Builder<OccupiedSuit> builder = ImmutableList.builder();

  while (suitsIter.hasNext() && pilotsIter.hasNext()) {
    builder.add(new OccupiedSuit(suitsIter.next(), pilotsIter.next()));
  }
  // Most of the existing solutions fail to enforce that the lists are the same
  // size. That is a *classic* source of bugs. Always enforce your invariants!
  checkArgument(!suitsIter.hasNext(),
      "Unexpected extra suits: %s", ImmutableList.copyOf(suitsIter));
  checkArgument(!pilotsIter.hasNext(),
      "Unexpected extra pilots: %s", ImmutableList.copyOf(pilotsIter));
  return builder.build();
}

Теперь вам не нужно поддерживать сложную пользовательскую реализацию Iterator - просто положитесь на уже существующую!


Мы также можем обобщить assignPilots() в универсальную утилиту, которая работает для любых двух входных данных, например так:

public static <L,R,M> List<M> zipLists(
    BiFunction<L,R,M> factory, Iterable<L> left, Iterable<R> right) {
  Iterator<L> lIter = left.iterator();
  Iterator<R> rIter = right.iterator();
  ImmutableList.Builder<M> builder = ImmutableList.builder();

  while (lIter.hasNext() && rIter.hasNext()) {
    builder.add(factory.apply(lIter.next(), rIter.next()));
  }

  checkArgument(!lIter.hasNext(),
      "Unexpected extra left elements: %s", ImmutableList.copyOf(lIter));
  checkArgument(!rIter.hasNext(),
      "Unexpected extra right elements: %s", ImmutableList.copyOf(rIter));
  return builder.build();
}

Который вы бы тогда вызывали так:

List<OccupiedSuit> occupiedSuits = zipLists(OccupiedSuit::new, suits, pilots);

В примере кода используются Guava * Preconditions и ImmutableList - если вы не используете Guava, достаточно просто встроить и переключиться на ArrayList, но просто используйте Guava: )

1 голос
/ 29 марта 2013
for(int i=0; i < mobileSuits.size(); i++) {
  MobileSuit suit = mobileSuits.get(i);
  Pilot pilot = pilots.get(i);
  ...
}
1 голос
/ 29 июня 2010

Почему бы не иметь класс MannedMobileSuit в качестве подкласса MobileSuit, который содержит экземпляр пилота? Это решит вашу проблему с помощью метода getPilot.

Обычно, когда возникают такие проблемы (требующие возврата двух экземпляров), это происходит потому, что ваша объектная модель не подходит и должна быть изменена. Держите настройки открытыми

0 голосов
/ 16 мая 2019

Улучшение ответа на user2224844, вот простая версия, которая попытается не столкнуться с исключением:

final Iterator<String> pilotIterator = pilots.iterator();
            mobileSuits.forEach(m -> {
                    Pilot p = pilotIterator.hasNext()? pilotIterator.next():nullOrWahtever;
<Now do your work with m and p variables>
    ...
    });
0 голосов
/ 27 ноября 2015

Наткнулся на эту страницу, пытаясь решить эту проблему, и оказалось, что есть библиотека, которая уже решила ее, используя потоки Java 8 (ознакомьтесь с функцией Zip).поток, просто позвонив list.stream()

https://github.com/poetix/protonpack

Stream<String> streamA = Stream.of("A", "B", "C");
Stream<String> streamB  = Stream.of("Apple", "Banana", "Carrot", "Doughnut");
List<String> zipped = StreamUtils.zip(streamA,
                                      streamB,
                                      (a, b) -> a + " is for " + b)
                                 .collect(Collectors.toList());

assertThat(zipped,
           contains("A is for Apple", "B is for Banana", "C is for Carrot"));
0 голосов
/ 29 июня 2010

Разве этого не достаточно?

for(MobileSuit ms : MobileSuits) {
    for(Pilot p : pilots){
        //TODO
    }
}
...