【问题标题】:Java - Iterating over every two elements in a listJava - 遍历列表中的每两个元素
【发布时间】:2013-04-08 15:52:14
【问题描述】:

在同时处理 2 个元素的同时迭代列表的最佳方法是什么?

例子:

List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
for(int i = 0; i < strings.size(); i++){
    String first = strings.get(i);
    String second = null;
    if(strings.size() > i + 1){
        second = strings.get(i + 1);
    }
    System.out.println("First [" + first + "] - Second [" + second + "]");
}

结果:

First [item 1] - Second [item 2]
First [item 2] - Second [item 3]
First [item 3] - Second [item 4]
First [item 4] - Second [null]

我想实现:

First [item 1] - Second [item 2]
First [item 3] - Second [item 4]

【问题讨论】:

    标签: java iterator


    【解决方案1】:

    只需将i 增加 2:

    for(int i = 0; i < strings.size() - 2; i += 2) {
        String first = strings.get(i);
        String second = strings.get(i+1);
    

    【讨论】:

    • 这不能越界吗?
    【解决方案2】:

    需要修改并增加i为第二个值,修改语句:

    second = strings.get(i + 1);
    

    second = strings.get(++i);
    

    这也会增加i,因为这似乎是所需的行为。

    所以你的代码是:

    List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
    for(int i = 0; i < strings.size(); i++){
        String first = strings.get(i);
        String second = null;
        if(strings.size() > i + 1){
            second = strings.get(++i); //Change here
        }
        System.out.println("First [" + first + "] - Second [" + second + "]");
    }
    

    【讨论】:

    • 我只会在 for 语句的第三部分继续更改索引变量。在循环内的多个位置增加它会导致代码不太清晰(意味着更难理解和维护,更容易出现错误)。这是 for 循环非常常见的习惯用法/经验法则,如果您按照自己的方式编写,while 循环可能是更好的选择。
    • @hyde,我考虑过,但后来决定更改最小值,为了 OP 的理解,我同意 for 循环计数器应该只在一个地方修改,因为它提供了更好的理解/可读性
    • 谢谢大家,两个答案都非常有帮助!
    【解决方案3】:

    我使用 Java8 BiConsumer 创建了以下方法:

    public static <T> void tupleIterator(Iterable<T> iterable, BiConsumer<T, T> consumer) {
        Iterator<T> it = iterable.iterator();
        if(!it.hasNext()) return;
        T first = it.next();
    
        while(it.hasNext()) {
            T next = it.next();
            consumer.accept(first, next);
            first = next;
        }
    }
    

    像这样使用它:

    List<String> myIterable = Arrays.asList("1", "2", "3");
    tupleIterator(myIterable, (obj1, obj2) -> {
        System.out.println(obj1 + " " + obj2);
    });
    

    这将输出:

    1 2
    2 3
    

    【讨论】:

      【解决方案4】:
      List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");    
      int i = 0;  
      for(; i < strings.size() - 1; i+=2){  
          String first = strings.get(i);  
          String second =  strings.get(i + 1);  
          System.out.println("First [" + first + "] - Second [" + second + "]");  
      }  
      //For odd sized lists
      if(i < strings.size()){         
          System.out.println("First [" + strings.get(i) + "]");  
      }
      

      【讨论】:

      • int i = 0 放在循环之外有什么原因吗?
      • @MarcoForberg:Yes.To 在for 循环之外检查奇数大小的列表
      • 嗯好吧,但为什么不像往常一样检查奇数大小:strings.size() % 2?嗯......如果我错了,请纠正我,但在执行循环后,我认为我将比列表的大小大一。
      • @MarcoForberg:你也可以这样做
      • 你忘记在你的 if 块中设置一个新值
      【解决方案5】:

      如果在每次迭代中将 i 增加 2 会怎样?应该做... 否则考虑在实际循环中增加 i

      【讨论】:

        【解决方案6】:

        我们当然应该为一般情况提供解决方案;-)

        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
            for (Pair<Integer> p : Pair.over(list)) {
                System.out.printf("%d, %d\n", p.first, p.second);
            }
        }
        
        static class Pair<T> {
            T first;
        
            T second;
        
            public Pair(T first, T second) {
                this.first = first;
                this.second = second;
            }
        
            public static <T> Iterable<Pair<T>> over(Collection<T> collection) {
                return new PairWise<T>(collection);
            }
        
            private static class PairWise<T> implements Iterable<Pair<T>>, Iterator<Pair<T>> {
        
                final Iterator<T> iterator;
        
                PairWise(Collection<T> collection) {
                    super();
                    this.iterator = collection.iterator();
                }
        
                @Override
                public Iterator<Pair<T>> iterator() {
                    return this;
                }
        
                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }
        
                @Override
                public Pair<T> next() {
                    T first = null;
                    T second = null;
                    if (iterator.hasNext())
                        first = iterator.next();
                    else
                        throw new NoSuchElementException();
                    if (iterator.hasNext())
                        second = iterator.next();
                    return new Pair<T>(first, second);
                }
        
                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
        
            }
        }
        

        【讨论】:

          【解决方案7】:

          您可以避免使用Iterator 进行索引;这适用于任何Iterable,而不仅仅是列表。只需获取一个迭代器并在每次循环迭代时将其递增两次:

          List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
          Iterator<String> stringsIterator = strings.iterator();
          while (stringsIterator.hasNext()) {
            String first = stringsIterator.next();
            String second = stringsIterator.next();
            System.out.println("First [" + first + "] - Second [" + second + "]");
          }
          

          这假设一个偶数长度的列表,如果它是奇数长度,则在最后一次传递时抛出NoSuchElementException。您可以通过多种方式处理此问题:

          • 使用try-catch;
          • 有一个保护子句,可以预先检查长度是否相等;
          • 在获取第二个元素之前进行检查。

          检查第二个元素:

          List<String> strings = Arrays.asList("item 1", "item 2", "item 3");
          Iterator<String> stringsIterator = strings.iterator();
          while (stringsIterator.hasNext()) {
            String first = stringsIterator.next();
            String second = stringIterator.hasNext() ? stringIterator.next() : null;
            System.out.println("First [" + first + "] - Second [" + second + "]");
          }
          

          迭代器会使某些人感到困惑,因此您还可以使用带有分支的 for-each 循环和用于奇偶校验的辅助触发器变​​量。这更糟糕,因为它使循环的逻辑变得更加复杂,以简化迭代:与其每次通过循环执行一次操作,按顺序且没有分支,而是必须经过两次并在心理上进行分支。请注意,如果它的长度为奇数,则会跳过最后一个元素;如果也想处理这些情况,可以在之后添加isFirst 的检查。

          List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
          boolean isFirst = true;
          String first = null;
          String second = null;
          for (String string : strings) {
            if (isFirst) {
              first = string;
              isFirst = false;
            } else {
              second = string;
              isFirst = true;
              System.out.println("First [" + first + "] - Second [" + second + "]");
            }
          }
          

          最后,请注意所有这些迭代器和辅助变量都有超出范围(它们仅用于循环本身,因此会污染本地环境):它们可以包装在块中以限制范围,尽管通常结果嵌套被认为比超出范围更糟糕:

          List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
          {
            Iterator<String> stringsIterator = strings.iterator();
            while (stringsIterator.hasNext()) {
              String first = stringsIterator.next();
              String second = stringsIterator.next();
              System.out.println("First [" + first + "] - Second [" + second + "]");
            }
          }
          

          【讨论】:

          • 这是一个基本问题,但一个干净的答案(没有索引)有点微妙。这个问题在现实生活中出现(并且出现在我身上,这就是我来到这里的原因),当你有一个扁平对的列表时,比如[a0, b0, a1, b1, ...]
          【解决方案8】:

          现在在 Java 8 中,使用 https://github.com/wapatesh/fig

          你可以写:

          seq.forEachSlice(2, (values)->{
              // [1,2]  [3, 4]
          });
          

          【讨论】:

            【解决方案9】:
            for(int i = 0; i < strings.size(); i++){
                String first = strings.get(i++);
                String second = null;
                if(strings.size() > i){
                    second = strings.get(i);
                }
                System.out.println("First [" + first + "] - Second [" + second + "]");
            }
            

            【讨论】:

              【解决方案10】:
              List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
              for(int i = 0; i < strings.size(); i++){
                  if(i½2 = 0){
                      String first = strings.get(i);
                      System.out.print("First [" + first + "] ");
                  }else{
                       String second = strings.get(i + 1);
                       System.out.println("- Second [" + second + "]");
                  }
              }
              

              【讨论】:

                【解决方案11】:

                为了性能,我建议你只计算一个列表的大小,而不是在每个新循环中创建一个新字符串。

                List<String> strings = Arrays.asList("item 1", "item 2", "item 3", "item 4");
                int length = strings.size();
                String first, second = null;
                for(int i = 0; i < length; i += 2){
                    ...
                }
                

                【讨论】:

                • 意见:对strings.size() 的调用被内联,因此性能差异可以忽略不计。同样经常(可能不是这种情况)这样的优化有可能成为错误,当稍后更改代码时,忘记更新预先计算的值。 考虑这种优化很有用,但只有如果真正改善了性能(例如,计算大小很昂贵)或可读性(例如,大小是在几个地方计算的)。否则,像这里一样,这是过早的微优化,应该避免使用 IMNSHO。
                • 我同意你的观点,对于解决方案的第一个原型,你不会考虑优化。但在现实世界的情况下,您要处理大量数据。所以我认为这应该被认为是一种好的做法
                • “在现实世界中你处理大量数据集”在我看来有点笼统。需要性能优化的大型数据集非常出色,即使在现实世界中也是如此!
                • strings.size() 即使没有将其分配给变量,也只会被评估一次......您不是“在每个循环中创建一个新字符串”,您正在创建一个新的局部变量引用。在循环内创建局部变量引用根本不会损害性能。
                猜你喜欢
                • 2018-03-14
                • 1970-01-01
                • 1970-01-01
                • 2014-11-09
                • 1970-01-01
                • 2022-08-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多