【问题标题】:Iterating through a list in reverse order in java在java中以相反的顺序遍历列表
【发布时间】:2011-01-07 07:59:13
【问题描述】:

我正在迁移一段代码以使用泛型。这样做的一个论点是,for 循环比跟踪索引或使用显式迭代器要干净得多。

在大约一半的情况下,现在使用索引以相反的顺序迭代列表(一个 ArrayList)。

有人可以建议一种更简洁的方法(因为我不喜欢使用集合时的indexed for loop),尽管它确实有效?

 for (int i = nodes.size() - 1; i >= 0; i--) {
    final Node each = (Node) nodes.get(i);
    ...
 }

注意:我不能在 JDK 之外添加任何新的依赖项。

【问题讨论】:

  • 使用显式索引迭代索引数据结构有什么不好?至少它告诉你到底发生了什么。为了向后迭代,我总是使用以下略短的习语:for (int i = nodes.size(); --i >= 0;)
  • 没什么特别的,我宁愿编程到一个界面,不知道我正在使用什么样的列表。虽然我很喜欢你的短手。 (+1 条评论)
  • @x4u:虽然 Iterator 快速失败并且允许在迭代过程中轻松删除元素,但其中并没有太多内容。
  • 这个类被破坏了,因为用户可能想要对同一个 Iterable 进行第二次迭代,或者列表可能会在构造迭代和迭代之间发生变化。我敢肯定,出于您的目的,您只需确保不这样做,但修复代码不会太难;或者只是从 Guava 中窃取代码(Apache 2.0 许可证):code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
  • 很公平,但如果我没看错的话,即使是番石榴也容易受到同样的影响。如果用户保留反向结果的副本,它会遇到同样的问题。

标签: java collections


【解决方案1】:
Valid for Java 9+

List<String> strList = List.of("a", "b", "c", "d", "e");

IntStream.iterate(strList.size() - 1, i -> i >= 0, i -> --i)
         .mapToObj(strList::get)
         .forEach(System.out::println);

【讨论】:

    【解决方案2】:

    【讨论】:

    • 您的链接已不存在
    • 谢谢@AaA;看起来ReverseListIterator 链接在这一点上确实有效。 (2020 年 12 月更新)
    【解决方案3】:

    这是一个老问题,但它缺乏对 java8 友好的答案。在 Streaming API 的帮助下,以下是一些反向迭代列表的方法:

    List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 3, 3, 7, 5));
    list.stream().forEach(System.out::println); // 1 3 3 7 5
    
    int size = list.size();
    
    ListIterator<Integer> it = list.listIterator(size);
    Stream.generate(it::previous).limit(size)
        .forEach(System.out::println); // 5 7 3 3 1
    
    ListIterator<Integer> it2 = list.listIterator(size);
    Stream.iterate(it2.previous(), i -> it2.previous()).limit(size)
        .forEach(System.out::println); // 5 7 3 3 1
    
    // If list is RandomAccess (i.e. an ArrayList)
    IntStream.range(0, size).map(i -> size - i - 1).map(list::get)
        .forEach(System.out::println); // 5 7 3 3 1
    
    // If list is RandomAccess (i.e. an ArrayList), less efficient due to sorting
    IntStream.range(0, size).boxed().sorted(Comparator.reverseOrder())
        .map(list::get).forEach(System.out::println); // 5 7 3 3 1
    

    【讨论】:

    • 非常好,只是表面上的改变: int size = list.size(); ListIterator it = list.listIterator(size); Stream.generate(it::previous).limit(size).forEach(System.out::println);
    【解决方案4】:

    创建自定义reverseIterable

    【讨论】:

    • 不知道为什么这会被否决,我可能只是为执行此操作的列表制作一个包装器。
    • 这不亚于调用 Collections.reverse() 恕我直言。
    • @Allain:同意。尽管我不明白为什么您将对 listIterator(list.size()) 的调用视为“不干净”,但还是不赞成。即使你包装它,你仍然需要在某处进行相同的方法调用。
    • 假设不是,只是不愿意为了清洁而受到性能影响。
    • 投了反对票,因为我认为这不是一个完整的答案。
    【解决方案5】:

    Guava 提供Lists#reverse(List)ImmutableList#reverse()。与 Guava 的大多数情况一样,如果参数是 ImmutableList,则前者委托给后者,因此您可以在所有情况下使用前者。这些不会创建列表的新副本,而只是“反转视图”。

    例子

    List reversed = ImmutableList.copyOf(myList).reverse();
    

    【讨论】:

      【解决方案6】:

      如果列表相当小以至于性能不是一个真正的问题,可以使用Google GuavaLists-class 的reverse-metod。产生漂亮的for-each-code,原始列表保持不变。此外,反向列表由原始列表支持,因此对原始列表的任何更改都将反映在反向列表中。

      import com.google.common.collect.Lists;
      
      [...]
      
      final List<String> myList = Lists.newArrayList("one", "two", "three");
      final List<String> myReverseList = Lists.reverse(myList);
      
      System.out.println(myList);
      System.out.println(myReverseList);
      
      myList.add("four");
      
      System.out.println(myList);
      System.out.println(myReverseList);
      

      产生以下结果:

      [one, two, three]
      [three, two, one]
      [one, two, three, four]
      [four, three, two, one]
      

      也就是说myList的反向迭代可以写成:

      for (final String someString : Lists.reverse(myList)) {
          //do something
      }
      

      【讨论】:

        【解决方案7】:

        选项 1:您是否考虑过使用 Collections#reverse() 反转列表,然后使用 foreach?

        当然,您可能还想重构您的代码,以使列表正确排序,这样您就不必颠倒它,这会占用额外的空间/时间。


        编辑:

        选项 2:或者,您可以使用 Deque 代替 ArrayList 吗?它将允许您向前和向后迭代


        编辑:

        选项 3:正如其他人所建议的,您可以编写一个反向遍历列表的迭代器,这是一个示例:

        import java.util.Iterator;
        import java.util.List;
        
        public class ReverseIterator<T> implements Iterator<T>, Iterable<T> {
        
            private final List<T> list;
            private int position;
        
            public ReverseIterator(List<T> list) {
                this.list = list;
                this.position = list.size() - 1;
            }
        
            @Override
            public Iterator<T> iterator() {
                return this;
            }
        
            @Override
            public boolean hasNext() {
                return position >= 0;
            }
        
            @Override
            public T next() {
                return list.get(position--);
            }
        
            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        
        }
        
        
        List<String> list = new ArrayList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");
        
        for (String s : new ReverseIterator<String>(list)) {
            System.out.println(s);
        }
        

        【讨论】:

        • 已经考虑过了,但是扭转它的成本太高了。此外,它只需要大约一半的时间进行这种迭代。翻转它只会将问题转移到另一半。
        • + 代表双端队列;典型的实现有descendingIterator()
        • 这对于链表来说会很糟糕,OP 的变体更好。
        • 在我看来,使用for each 表达式是最惯用的解决方案。很高兴意识到如果您的 List 以向后迭代的方式实现 Iterable ,这是可能的。我将使用这种方法并使用 Apache Commons Collections 中的 ReverseListIterator 类。
        【解决方案8】:

        拥有如下所示的代码:

        List<Item> items;
        ...
        for (Item item : In.reverse(items))
        {
            ...
        }
        

        将此代码放入名为“In.java”的文件中:

        import java.util.*;
        
        public enum In {;
            public static final <T> Iterable<T> reverse(final List<T> list) {
                return new ListReverseIterable<T>(list);
            }
        
            class ListReverseIterable<T> implements Iterable<T> {
                private final List<T> mList;
        
                public ListReverseIterable(final List<T> list) {
                    mList = list;
                }
        
                public Iterator<T> iterator() {
                    return new Iterator<T>() {
                        final ListIterator<T> it = mList.listIterator(mList.size());
        
                        public boolean hasNext() {
                            return it.hasPrevious();
                        }
                        public T next() {
                            return it.previous();
                        }
                        public void remove() {
                            it.remove();
                        }
                    };
                }
            }
        }
        

        【讨论】:

        • 这在线程的其他地方已经提到过,但是listIterator 字段需要在Iterator 实现中,而不是Iterable 实现中。
        • 为什么使用枚举类型而不是类?
        • 它使“In”类不可实例化,而无需编写私有默认构造函数。适用于只有静态方法的类。
        • 我会说滥用枚举只是为了避免编写私有默认构造函数充其量是令人困惑的。将枚举用于枚举,将类用于类如何?例如,通过将一个类变成一个枚举,它还隐式地获得了一个name() 方法、一个ordinal() 方法和一个static valueOf() 方法。
        • 是的,您可以继续您的意见,这也可以正常工作,但我认为恰恰相反。类用于实例化对象并通过包含私有默认构造函数来禁止它们的实例化来滥用它们充其量是令人困惑的。在 Java 中,枚举实际上是类,但不能通过设计实例化。
        【解决方案9】:

        正如至少两次建议的那样,您可以将descendingIteratorDeque 一起使用,尤其是与LinkedList 一起使用。如果你想使用 for-each 循环(即有一个Iterable),你可以像这样构造和使用一个包装器:

        import java.util.*;
        
        public class Main {
        
            public static class ReverseIterating<T> implements Iterable<T> {
                private final LinkedList<T> list;
        
                public ReverseIterating(LinkedList<T> list) {
                    this.list = list;
                }
        
                @Override
                public Iterator<T> iterator() {
                    return list.descendingIterator();
                }
            }
        
            public static void main(String... args) {
                LinkedList<String> list = new LinkedList<String>();
                list.add("A");
                list.add("B");
                list.add("C");
                list.add("D");
                list.add("E");
        
                for (String s : new ReverseIterating<String>(list)) {
                    System.out.println(s);
                }
            }
        }
        

        【讨论】:

          【解决方案10】:

          原因:“不知道为什么ArrayList没有descendingIterator...”

          由于数组列表不保持列表与数据添加到列表中的顺序相同。所以,永远不要使用 Arraylist 。

          链表将保持数据与添加到列表相同的顺序。

          所以,在我上面的例子中,我使用了 ArrayList() 来让用户改变他们的想法,让他们从他们身边锻炼一些东西。

          不是这个

          List<String> list = new ArrayList<String>();
          

          使用:

          List<String> list = new LinkedList<String>();
          
          list.add("ravi");
          
          list.add("kant");
          
          list.add("soni");
          
          // Iterate to disply : result will be as ---     ravi kant soni
          
          for (String name : list) {
            ...
          }
          
          //Now call this method
          
          Collections.reverse(list);
          
          // iterate and print index wise : result will be as ---     soni kant ravi
          
          for (String name : list) {
            ...
          }
          

          【讨论】:

          • “由于数组列表不保持列表的顺序与将数据添加到列表中的顺序相同”嗯,是的,至少它与链表的方式相同。你能举个例子说明他们不这样做吗?
          • 是的,ArrayList 和 LinkedList(一般的 List 合约)保持插入的项目按顺序排列。 Set 合约是无序的或按排序(TreeSet)排序的。反过来的想法也不错,但请记住,它实际上会重新排序列表,这可能会很慢。
          • 我对这个答案投了反对票,因为它错误地指出 ArrayLists 不按插入顺序保留项目。正如@bill-k 所指出的,所有列表都是按定义排序的集合。使用 LinkedList 的建议有其优点,因为它具有 descendingIterator() 方法,但前提是性能比内存和抽象更受关注。
          【解决方案11】:

          非常简单的例子:

          List<String> list = new ArrayList<String>();
          
          list.add("ravi");
          
          list.add("kant");
          
          list.add("soni");
          
          // Iterate to disply : result will be as ---     ravi kant soni
          
          for (String name : list) {
            ...
          }
          
          //Now call this method
          
          Collections.reverse(list);
          
          // iterate and print index wise : result will be as ---     soni kant ravi
          
          for (String name : list) {
            ...
          }
          

          【讨论】:

            【解决方案12】:

            您可以使用具体类LinkedList 而不是通用接口List。然后你有一个descendingIterator 用于反向迭代。

            LinkedList<String > linkedList;
            for( Iterator<String > it = linkedList.descendingIterator(); it.hasNext(); ) {
                String text = it.next();
            }
            

            不知道为什么没有descendingIteratorArrayList...

            【讨论】:

              【解决方案13】:

              还找到了google集合reverse方法。

              【讨论】:

              • 哪个版本的谷歌收藏?您的链接已不存在。
              【解决方案14】:

              这是ReverseIterable 的(未经测试的)实现。当iterator() 被调用时,它会创建并返回一个私有的ReverseIterator 实现,它简单地将对hasNext() 的调用映射到hasPrevious(),对next() 的调用映射到previous()。这意味着您可以反向迭代 ArrayList,如下所示:

              ArrayList<String> l = ...
              for (String s : new ReverseIterable(l)) {
                System.err.println(s);
              }
              

              类定义

              public class ReverseIterable<T> implements Iterable<T> {
                private static class ReverseIterator<T> implements Iterator {
                  private final ListIterator<T> it;
              
                  public boolean hasNext() {
                    return it.hasPrevious();
                  }
              
                  public T next() {
                    return it.previous();
                  }
              
                  public void remove() {
                    it.remove();
                  }
                }
              
                private final ArrayList<T> l;
              
                public ReverseIterable(ArrayList<T> l) {
                  this.l = l;
                }
              
                public Iterator<T> iterator() {
                  return new ReverseIterator(l.listIterator(l.size()));
                }
              }
              

              【讨论】:

              • 对我来说看起来不错,尽管将其公开为静态方法而不是公共构造函数会(在大多数情况下)避免客户端指定类型参数的需要。番石榴就是这样做的..
              • 这是最好的实现,但是ReverseIterator 缺少必要的构造函数,代码应该使用List 而不是ArrayList
              【解决方案15】:

              试试这个:

              // Substitute appropriate type.
              ArrayList<...> a = new ArrayList<...>();
              
              // Add elements to list.
              
              // Generate an iterator. Start just after the last element.
              ListIterator li = a.listIterator(a.size());
              
              // Iterate in reverse.
              while(li.hasPrevious()) {
                System.out.println(li.previous());
              }
              

              【讨论】:

              • 不错。不使用索引,但失去了 for each 语法的优雅。无论如何 +1。
              • 在没有索引参数的情况下调用 listIterator() 将在列表的开头给出一个迭代器,因此 hasPrevious() 将在第一次调用时返回 false。
              • 我想你想要一个关于listIterator 调用的索引。
              • 您可以编写一个 Iterator 反向使用 ListIterator,但这对于一个循环可能不值得。
              • 这不是一个循环,所以我已经把它包装起来了。 pastebin.ca/1759041 所以,现在我可以做for (Node each : new ListReverse&lt;Node&gt;(nodes)) { }
              【解决方案16】:

              我认为使用 for 循环语法是不可能的。我唯一能建议的就是做这样的事情:

              Collections.reverse(list);
              for (Object o : list) {
                ...
              }
              

              ...但我不会说这是“更清洁”,因为它的效率会降低。

              【讨论】:

              • 它还会改变你遍历的列表,这是一个相当大的副作用。 (假设你将它包装在一个方法中,每次调用它时你都会以另一种方式遍历列表^^)
              • 这是最干净的解决方案。
              猜你喜欢
              • 2010-10-06
              • 2018-03-08
              • 2011-11-02
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-08-17
              相关资源
              最近更新 更多