Provide an iterator over the contents of two lists simultaneously?

前端 未结 11 2074
逝去的感伤
逝去的感伤 2020-12-03 04:45

Suppose I have this:

public class Unit {

    ...

    List mobileSuits;
    List pilots;

    ...
}
         


        
11条回答
  •  青春惊慌失措
    2020-12-03 05:27

    Basically, assume MobileSuit and Pilot need to be separated.

    That's fine, but here you're trying to treat them as a unit, so structure your code that way. The suggestions above use a Pair class or Map.Entry, but it's much better to provide a clearly-named object that represents a MobileSuit with a Pilot, e.g.:

    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
    }
    

    Then, rather than constructing a custom Iterator/Iterable, just write a helper function that zips up the two lists. For example:

    public static List assignPilots(
        Iterable suits, Iterable pilots) {
      Iterator suitsIter = suits.iterator();
      Iterator pilotsIter = pilots.iterator();
      ImmutableList.Builder 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();
    }
    

    Now you don't need to maintain a complex custom Iterator implementation - just rely on one that already exists!


    We can also generalize assignPilots() into a generic utility that works for any two inputs, like so:

    public static  List zipLists(
        BiFunction factory, Iterable left, Iterable right) {
      Iterator lIter = left.iterator();
      Iterator rIter = right.iterator();
      ImmutableList.Builder 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();
    }
    

    Which you'd then invoke like so:

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

    Example code uses Guava's Preconditions and ImmutableList - if you don't use Guava it's easy enough to inline and swap to ArrayList, but just use Guava :)

提交回复
热议问题