ChatGPT解决这个技术问题 Extra ChatGPT

在java中以相反的顺序遍历列表

我正在迁移一段代码以使用泛型。这样做的一个论点是,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 进行第二次迭代,或者列表可能会在构建 iterable 和迭代时发生变化。我敢肯定,出于您的目的,您只需确保不这样做,但修复代码不会太难;或者只是从 Guava 中窃取代码(Apache 2.0 许可证):code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
很公平,但如果我没看错的话,即使是番石榴也容易受到同样的影响。如果用户保留反向结果的副本,它会有同样的问题。

J
John Feminella

尝试这个:

// 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 调用的索引。
您可以编写一个反向使用 ListIteratorIterator,但这对于一个循环可能不值得。
这不是一个循环,所以我已经把它包起来了。 pastebin.ca/1759041 所以,现在我可以做for (Node each : new ListReverse<Node>(nodes)) { }
I
Iulian Popescu

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

例子

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

A
Adamski

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

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

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


它还会更改您遍历的列表,这是一个巨大的副作用。 (假设你将它包装在一个方法中,每次调用它时你都会以另一种方式遍历列表^^)
这是最干净的解决方案。
r
rogerdpack

选项 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 类。
s
sth

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

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

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


f
fps

这是一个老问题,但它缺乏对 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);
A
Adamski

这是 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
T
Tobb

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

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
}

d
df778899

您可以使用 Apache Commons-Collections 中的 ReverseListIterator

https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/iterators/ReverseListIterator.html


你的链接不存在了
谢谢@AaA;看起来 ReverseListIterator 链接在这一点上确实有效。 (2020 年 12 月更新)
B
Bo Persson

非常简单的例子:

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) {
  ...
}

P
Paulo Mattos

创建自定义 reverseIterable


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

还找到了 google collections reverse 方法。


哪个版本的谷歌收藏?您的链接不再存在。
A
Aubin

拥有如下所示的代码:

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 中,枚举实际上是类,但不能通过设计实例化。
m
masterxilo

正如至少两次建议的那样,您可以将 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);
        }
    }
}

R
Rupesh Goyal
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);

T
Thiem Nguyen

原因:“不知道为什么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() 方法,但前提是性能比内存和抽象更受关注。