【问题标题】:Provide an iterator over the contents of two lists simultaneously?同时提供两个列表内容的迭代器?
【发布时间】:2011-03-09 10:52:02
【问题描述】:

假设我有这个:

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 需要分开。

【问题讨论】:

  • 如果有没有飞行员的机动战士,我假设你的机动战士比飞行员多。这可能会使您对 hasNext() 的检查有问题,因为只有在两个列表中都有更多项目时它才会返回 true。如果您能提供更多关于如何将两者结合起来的标准的信息,我认为这将有助于回答您的问题。
  • @Rob Cooney 是的,既然你提到了它,那 hasNext() 至少是错误的。
  • OMG +1 回答高达式的问题。
  • 非常有趣的问题。在stackoverflow.com/questions/1115563/… 之前我没有听说过拉链,所以我想知道“为什么番石榴不支持它?”显然它是在内部进行的。已经有一些关于在外部支持它的讨论-见code.google.com/p/guava-libraries/issues/detail?id=35这里有一个相关问题stackoverflow.com/questions/5278040/…
  • 对于遍历并行集合的一般情况,请参阅How to most elegantly iterate through parallel collections?,虽然这里有所不同,因为您想在 类之外进行迭代。

标签: java generics iterator


【解决方案1】:

原因是可能有没有飞行员的机动战士,我不知道如何通过让它们保持在同一级别来解决这个问题。

你可以使用空值,对吧?这是正确的做法 - 让每套西装跟踪其飞行员。如果它没有飞行员,则在那里用空值表示。

但是,如果你因为某种原因坚决不这样做......

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

    public SuitAndPilot(Suit s, Pilot p) {
           suit = s;
           pilot = p;
    }
}

【讨论】:

    【解决方案2】:

    另外,我无法创建一个新课程来结合 MobileSuit 和 Pilot。

    这听起来不正确。听起来您不能替换 MobileSuit 和 Pilot 由单个类,但我看不出有任何理由为什么您不能有一个组合它们的类- 即只有一个getPilot() 方法和一个getMobileSuit() 方法。您可以使用通用的 Pair 类来实现相同的目的,但自定义类会更容易使用。

    另一方面,如果您想在多个地方进行这种“压缩”操作,这可能是一种解决方案。或者,您可以编写一个通用接口来表示组合两个不同项目的行为 - 这可以返回 SuitedPilot 或任何您的组合类。

    【讨论】:

    • 更好的是,虽然 Pair 类在整个对象模型中可能很奇怪,但它确实是解决这个问题的一个很好的实用程序类。
    【解决方案3】:

    为什么不将 MannedMobileSuit 类作为 MobileSuit 的子类,其中包含一个 Pilot 的实例?这将通过使用 getPilot 方法解决您的问题。

    通常当您遇到此类问题(需要返回两个实例)时,这是因为您的对象模型不合适,应该更改。保持你的选择开放

    【讨论】:

    • 嗯,我试试看效果如何。
    【解决方案4】:

    无论如何,问题是我不能真正从 next() 返回单个对象,而且我也不能让迭代器采用多种类型。那么,有什么想法吗?

    显然,您将需要一个轻量级的“pair”类。这大致类似于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】:

      您可以只使用Map&lt;MobileSuit, Pilot&gt;,其中映射到MobileSuitnull 值表示没有飞行员。 Iterator 可能只是由map.entrySet().iterator() 检索到的Iterator&lt;Map.Entry&lt;MobileSuit, Pilot&gt;&gt;

      【讨论】:

      • 如何填充地图?你的意思是用地图替换这两个列表吗?如果需要原始列表,您可以使用 map.keySet() 和 map.values(),但是我们会失去排序。
      • 是的,我的意思是用地图替换这两个列表。很明显,这里需要的是 MobileSuits 和 Pilots 之间的关联,而这正是 Map 的用途。现在,如果可能有没有 MobileSuit 的飞行员以及其他方式,那么在仍然使用地图将两者关联起来的同时维护列表(可能会更好)可能是有意义的。
      • +1 如果这些对象实际上是紧密耦合的,则没有理由将它们存储在单独的集合中。 Map 确实稍微改变了语义(不允许重复),但总的来说这是一个更好的主意。
      【解决方案6】:

      这还不够吗?

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

      【讨论】:

      • 这是 O(n²) 而不是 O(n)。
      • 这不能回答问题。索引 i 处的 MobileSuit 和索引 i 处的 Pilot 之间存在隐式关系,这里的目标是提供一个暴露该关系的 Iterator .此代码将每个MobileSuit每个 Pilot 关联起来。
      【解决方案7】:

      这是从 Stephen 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 之前检查列表的大小。

      【讨论】:

        【解决方案8】:
        for(int i=0; i < mobileSuits.size(); i++) {
          MobileSuit suit = mobileSuits.get(i);
          Pilot pilot = pilots.get(i);
          ...
        }
        

        【讨论】:

        • 评论你的代码 sn-p 会让你的答案更清楚
        • 我认为对于习惯于 C 风格索引循环的人来说,这是最直接的解决方案,但我认为它不是那么优雅和高效。
        • 您不会检查两个列表的大小是否相同,这意味着如果mobileSuits 列表更长,您将获得IndexOutOfBoundsException,如果pilots 更长,您将默默地删除一些 Pilot 对象。
        • 最终迭代器 PilotIterator = Pilots.iterator(); mobileSuits.forEach(m -> { Pilot Pilot = PilotIterator.next()); });
        【解决方案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】:

          基本上,假设 MobileSuit 和 Pilot 需要分开。

          这很好,但是在这里您试图将它们视为一个单元,因此以这种方式构建您的代码。上述建议使用Pair 类或Map.Entry,但最好提供一个明确命名的对象,该对象代表MobileSuitPilot,例如:

          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,不如编写一个zips up the two lists的辅助函数。例如:

          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);
          

          示例代码使用 GuavaPreconditionsImmutableList - 如果您不使用 Guava,内联和交换到 ArrayList 很容易,但只需使用 Guava :)

          【讨论】:

            【解决方案11】:

            改进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>
                ...
                });
            

            【讨论】:

              猜你喜欢
              • 2021-04-29
              • 1970-01-01
              • 2014-02-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-23
              • 1970-01-01
              相关资源
              最近更新 更多