Обеспечить итератор над содержимым двух списков одновременно?
Предположим, что у меня есть это:
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(), и у меня также не может быть Iterator более одного типа. Итак, какие-то мысли?
Кроме того, я не могу создать новый класс для объединения MobileSuit и Pilot. Мне нужно, чтобы они были разделены, хотя я итерации по обоим за раз. Причина в том, что могут быть мобильные костюмы, у которых нет пилотов, и я не уверен, как это исправить, удерживая их в одном классе. Этот класс нужно обрабатывать в других местах, поэтому мне придется объединить интерфейс вокруг этого и многое другое. В принципе, предположим, что MobileSuit и Pilot необходимо разделить.
Ответы
Ответ 1
Во всяком случае, проблема в том, что я не могу реально вернуть только один объект из 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());
}
...
}
Примечание. Это явно не касается случаев, когда списки имеют разную длину. Что произойдет, так это то, что дополнительные элементы в конце более длинного списка будут игнорироваться.
Ответ 2
Это скопировано + отредактировано с ответа Стивена C. Не стесняйтесь использовать:
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.
Ответ 3
Кроме того, я не могу создать новый класс для объединения MobileSuit и Pilot.
Это звучит неправильно. Похоже, вы не можете заменить MobileSuit и Pilot одним классом, но я не вижу причин, по которым вы не можете использовать один класс, который их объединяет, то есть тот, у которого есть только метод getPilot()
и getMobileSuit()
. Вы можете использовать общий класс Pair
для той же цели, но пользовательский класс будет проще использовать.
С другой стороны, если вы хотите выполнить эту операцию "запирания" в нескольких местах, это может быть одно решение. В качестве альтернативы вы могли бы написать общий интерфейс для представления акта объединения двух разных элементов - который мог бы вернуть SuitedPilot
или любой другой класс вашей комбинации.
Ответ 4
Причина в том, что могут быть мобильные костюмы, у которых нет пилотов, и я не уверен, как это исправить, удерживая их в одном классе.
Вы можете использовать нулевые значения, правильно? Каков правильный способ сделать это - каждый костюм отслеживает свой пилот. Если у него нет пилот-сигнала, укажите, что с нулевым значением.
Но, если вы мертвы, когда не делаете этого по какой-то причине...
public class SuitAndPilot
{
public MobileSuit suit;
public Pilot pilot;
public SuitAndPilot(Suit s, Pilot p) {
suit = s;
pilot = p;
}
}
Ответ 5
Почему бы не создать класс MannedMobileSuit в качестве подкласса MobileSuit, который содержит экземпляр пилота? Это поможет решить вашу проблему с помощью метода getPilot.
Обычно, когда вы получаете такие проблемы (нужно вернуть два экземпляра), это потому, что ваша модель объекта не подходит и должна быть изменена. Сохраняйте свои возможности
Ответ 6
for(int i=0; i < mobileSuits.size(); i++) {
MobileSuit suit = mobileSuits.get(i);
Pilot pilot = pilots.get(i);
...
}
Ответ 7
В принципе, предположим, что нужно отключить 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 достаточно легко для inline и swap до ArrayList
, но просто используйте Guava:)
Ответ 8
Вы можете просто использовать Map<MobileSuit, Pilot>
, где значение null
, отображаемое на MobileSuit
, не указывает на отсутствие пилот-сигнала. Iterator
может быть просто Iterator<Map.Entry<MobileSuit, Pilot>>
, полученным map.entrySet().iterator()
.
Ответ 9
На этой странице попытался решить эту проблему, и выяснилось, что там есть библиотека, которая уже разрешила ее с использованием потоков 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"));
Ответ 10
Улучшение ответа от пользователя 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>
...
});
Ответ 11
Разве этого недостаточно?
for(MobileSuit ms : MobileSuits) {
for(Pilot p : pilots){
//TODO
}
}