Lặp lại thông qua một danh sách theo thứ tự ngược lại trong java


251

Tôi đang di chuyển một đoạn mã để sử dụng thuốc generic. Một đối số để làm như vậy là vòng lặp for sạch hơn nhiều so với theo dõi các chỉ mục hoặc sử dụng một trình vòng lặp rõ ràng.

Trong khoảng một nửa các trường hợp, danh sách (một ArrayList) đang được lặp lại theo thứ tự ngược lại bằng cách sử dụng một chỉ mục ngày hôm nay.

Ai đó có thể đề xuất một cách sạch hơn để làm điều này (vì tôi không thích indexed for loopkhi làm việc với các bộ sưu tập), mặc dù nó không hoạt động?

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

Lưu ý: Tôi không thể thêm bất kỳ phụ thuộc mới nào bên ngoài JDK.


10
Có gì tệ khi sử dụng một chỉ mục rõ ràng để lặp lại trên cấu trúc dữ liệu được lập chỉ mục? Ít nhất nó cho bạn thấy chính xác những gì đang xảy ra. Để lặp lại về phía sau, tôi luôn luôn có thành ngữ ngắn hơn một chút:for (int i = nodes.size(); --i >= 0;)
x4u

4
Không có gì đặc biệt, tôi chỉ muốn lập trình một giao diện và không biết tôi đang sử dụng loại danh sách nào. Mặc dù tôi rất thích bàn tay ngắn của bạn. (+1 bình luận)
Allain Lalonde

1
@ x4u: Không có nhiều trong đó mặc dù Iterator không nhanh và cũng cho phép các phần tử dễ dàng bị loại bỏ trong quá trình lặp.
Adamski

2
Lớp này bị hỏng, bởi vì người dùng có thể muốn lặp lại lần thứ hai trên cùng một lần lặp hoặc danh sách có thể thay đổi giữa khi lần lặp được xây dựng và khi nó lặp lại. Tôi chắc chắn cho mục đích của bạn, bạn sẽ đảm bảo không làm điều đó, nhưng sẽ không quá khó để sửa mã; hoặc chỉ lấy cắp mã từ Guava (giấy phép Apache 2.0): code.google.com/p/guava-lologists/source/browse/trunk/src/com/iêu
Kevin Bourrillion

1
Đủ công bằng, nhưng ngay cả quả ổi cũng dễ bị như vậy nếu tôi đọc đúng. Người dùng giữ một bản sao kết quả ngược lại, nó sẽ có cùng một vấn đề.
Allain Lalonde

Câu trả lời:


447

Thử cái này:

// 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());
}

4
Không tệ. Không sử dụng chỉ mục, nhưng mất đi sự thanh lịch của từng cú pháp. +1 dù sao đi nữa.
Allain Lalonde

26
Gọi listIterator () không có đối số chỉ mục sẽ cung cấp cho Iterator ở đầu danh sách và do đó hasPreingly () sẽ trả về false trong cuộc gọi đầu tiên.
Adamski

2
Bạn muốn một chỉ số trong listIteratorcuộc gọi, tôi nghĩ vậy.
Tom Hawtin - tackline

1
Bạn có thể viết một Iteratorcái sử dụng ListIteratorngược lại, nhưng nó có thể không có giá trị cho một vòng lặp.
Tom Hawtin - tackline

1
Đó không phải là một vòng lặp, vì vậy tôi đã lấy cái này và bọc nó lại. pastebin.ca/1759041 vì vậy, bây giờ tôi có thể làmfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde

35

Quả ổi cung cấp Lists#reverse(List)ImmutableList#reverse(). Như trong hầu hết các trường hợp cho Guava, các đại biểu trước cho sau này nếu đối số là một ImmutableList, vì vậy bạn có thể sử dụng cái trước trong mọi trường hợp. Những thứ này không tạo ra các bản sao mới của danh sách mà chỉ là "các quan điểm đảo ngược" của nó.

Thí dụ

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

23

Tôi không nghĩ rằng nó có thể sử dụng cú pháp vòng lặp for. Điều duy nhất tôi có thể đề nghị là làm một cái gì đó như:

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

... nhưng tôi sẽ không nói đây là "sạch" hơn vì nó sẽ kém hiệu quả hơn.


26
và nó cũng thay đổi vị trí danh sách bạn đi qua, đây là một tác dụng phụ khá lớn. (Giả sử bạn gói cái này theo một phương thức, mỗi lần nó được gọi là bạn duyệt qua danh sách theo cách khác ^^)
jolivier

Đây là giải pháp sạch nhất.
jfajunior

14

Tùy chọn 1: Bạn có nghĩ về việc đảo ngược Danh sách với Bộ sưu tập # Reverse () và sau đó sử dụng foreach chưa?

Tất nhiên, bạn cũng có thể muốn cấu trúc lại mã của mình sao cho danh sách được sắp xếp chính xác để bạn không phải đảo ngược mã, sử dụng thêm không gian / thời gian.


BIÊN TẬP:

Tùy chọn 2: Ngoài ra, bạn có thể sử dụng Deque thay vì ArrayList không? Nó sẽ cho phép bạn lặp đi lặp lại


BIÊN TẬP:

Tùy chọn 3: Như những người khác đã đề xuất, bạn có thể viết một Iterator sẽ đi qua danh sách ngược lại, đây là một ví dụ:

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

1
Đã nghĩ về nó, nhưng có chi phí đảo ngược nó là cấm. Ngoài ra, nó chỉ yêu cầu loại lặp này khoảng một nửa thời gian. Lật nó sẽ chỉ chuyển vấn đề sang nửa kia.
Allain Lalonde

3
+ cho Deque; thực hiện điển hình có descendingIterator().
thùng rác

Điều này sẽ thực hiện khủng khiếp cho các danh sách được liên kết, biến thể của OP là tốt hơn.
masterxilo

1
Sử dụng một for eachbiểu thức là giải pháp thành ngữ nhất theo ý kiến ​​của tôi. Thật tuyệt khi nhận ra rằng điều này là có thể nếu Danh sách của bạn thực hiện Iterable theo cách lặp đi lặp lại. Tôi sẽ sử dụng cách tiếp cận này và sử dụng lớp ReverseListIterator từ Bộ sưu tập Commons của Apache.
David Groomes 21/07 '

11

Bạn có thể sử dụng lớp cụ thể LinkedListthay vì giao diện chung List. Sau đó, bạn có một descendingIteratorlặp đi lặp lại với hướng ngược lại.

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

Không biết tại sao không descendingIteratorArrayList...


9

Đây là một câu hỏi cũ, nhưng nó thiếu một câu trả lời thân thiện với java8. Dưới đây là một số cách lặp lại danh sách, với sự trợ giúp của API truyền phát:

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

2
rất đẹp, chỉ là một thay đổi mỹ phẩm: int size = list.size (); ListIterator <Integer> it = list.listIterator (kích thước); Stream.generate (it :: trước) .limit (kích thước) .forEach (System.out :: println);
benez

5

Đây là một thực hiện (chưa được kiểm tra) của a ReverseIterable. Khi iterator()được gọi, nó tạo và trả về một ReverseIteratortriển khai riêng , chỉ đơn giản là ánh xạ các cuộc gọi hasNext()đến hasPrevious()và các cuộc gọi next()được ánh xạ tới previous(). Nó có nghĩa là bạn có thể lặp đi lặp ArrayListlại như sau:

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

Định nghĩa lớp

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

Có vẻ đúng với tôi, mặc dù phơi bày nó như một phương thức tĩnh thay vì một hàm tạo công khai (trong hầu hết các trường hợp) sẽ tránh được yêu cầu máy khách chỉ định tham số loại. Đây là cách Guava làm điều đó ..
Kevin Bourrillion

2
Đây là cách triển khai tốt nhất, tuy nhiên ReverseIteratorthiếu trình xây dựng cần thiết và mã nên sử dụng Listthay vì ArrayList.
Andy

5

Nếu các danh sách khá nhỏ để hiệu suất không phải là vấn đề thực sự, người ta có thể sử dụng reverse-metod của- Listsclass in Google Guava. for-eachMang lại mã khá , và danh sách ban đầu vẫn giữ nguyên. Ngoài ra, danh sách đảo ngược được hỗ trợ bởi danh sách ban đầu, vì vậy mọi thay đổi đối với danh sách ban đầu sẽ được phản ánh trong danh sách đảo ngược.

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

Mang lại kết quả sau:

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

Điều đó có nghĩa là phép lặp ngược của myList có thể được viết là:

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

5

Tạo một tùy chỉnh reverseIterable.


Không biết tại sao điều này lại bị bỏ phiếu, tôi chỉ có thể tạo một trình bao bọc cho danh sách thực hiện điều này.
Allain Lalonde

Điều này không kém phần sạch sẽ so với việc gọi Collections.reverse () IMHO.
Adamski

@ ALLain: Đồng ý lại. các downvote mặc dù tôi không thấy lý do tại sao bạn xem một cuộc gọi đến listIterator (list.size ()) là "không sạch". Ngay cả khi bạn bọc nó, bạn vẫn phải thực hiện cuộc gọi tương tự ở đâu đó.
Adamski

Giả sử không, chỉ là không sẵn sàng để thực hiện hit, cho sạch sẽ.
Allain Lalonde

18
Bỏ phiếu vì tôi không nghĩ rằng đây là một câu trả lời hoàn chỉnh.
Maarten Bodewes

3

Ví dụ rất đơn giản:

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



1

Để có mã trông như thế này:

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

Đặt mã này vào một tệp có tên "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();
                }
            };
        }
    }
}

1
Điều này đã được đề cập ở nơi khác trong luồng, nhưng listIteratortrường cần phải nằm trong Iteratortriển khai chứ không phải Iterablethực hiện.
Andy

1
Tại sao sử dụng một loại enum, không phải là một lớp?
Aubin

1
Nó làm cho lớp 'Trong' không thể thực hiện được mà không phải viết một hàm tạo mặc định riêng tư. Tốt cho các lớp chỉ có phương thức tĩnh.
intrepidis

Tôi sẽ nói rằng việc lạm dụng enums chỉ để tránh viết một hàm tạo mặc định riêng tư là khó hiểu nhất. Làm thế nào về việc sử dụng enums cho enums và các lớp cho các lớp? Bằng cách biến một lớp thành một enum, nó cũng mặc nhiên có được một name()phương thức, một ordinal()phương thức và một static valueOf()phương thức, chẳng hạn.
JHH

1
Vâng, bạn có thể tiếp tục với ý kiến ​​của mình và điều đó cũng sẽ hoạt động tốt, nhưng tôi nghĩ ngược lại. Các lớp là để khởi tạo các đối tượng và lạm dụng chúng bằng cách bao gồm một hàm tạo mặc định riêng để ngăn chặn việc khởi tạo của chúng là khó hiểu nhất. Trong Java, enums thực sự là các lớp, nhưng những cái không bao giờ có thể được khởi tạo bởi thiết kế.
intrepidis

0

Như đã được đề xuất ít nhất hai lần, bạn có thể sử dụng descendingIteratorvới a Deque, đặc biệt là với a LinkedList. Nếu bạn muốn sử dụng vòng lặp for- Iterableevery (nghĩa là có một ), bạn có thể xây dựng và sử dụng một trình bao bọc như thế này:

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

Lý do: "Không biết tại sao không có Trình giảm dần với ArrayList ..."

Vì danh sách mảng không giữ danh sách theo thứ tự như dữ liệu đã được thêm vào danh sách. Vì vậy, không bao giờ sử dụng Arraylist.

Danh sách được liên kết sẽ giữ dữ liệu theo thứ tự THÊM vào danh sách.

Vì vậy, ở trên trong ví dụ của tôi, tôi đã sử dụng ArrayList () để khiến người dùng vặn vẹo tâm trí và khiến họ tập luyện một thứ gì đó từ phía họ.

Thay vì cái này

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

SỬ DỤNG:

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

4
"Vì danh sách mảng không giữ danh sách theo thứ tự như dữ liệu đã được thêm vào danh sách" umm, đúng vậy, ít nhất nó cũng giống như danh sách được liên kết. Bạn có thể cung cấp một ví dụ về cách họ không?
corsiKa

Có, ArrayList và LinkedList (Hợp đồng Danh sách nói chung) giữ các mục theo thứ tự. hợp đồng Set không được sắp xếp hoặc sắp xếp theo thứ tự (Treeset). Ý tưởng ngược lại không tệ nhưng hãy nhớ rằng nó thực sự sắp xếp lại danh sách có thể chậm.
Hóa đơn

2
Tôi đã đánh giá thấp câu trả lời này vì nó nói sai rằng ArrayLists không giữ các mục theo thứ tự chèn. Theo ghi chú @ bill-k, tất cả các Danh sách được sắp xếp theo bộ sưu tập theo định nghĩa. Thay vào đó, đề xuất sử dụng LinkedList có giá trị, vì nó có phương thức desceinatingIterator (), nhưng chỉ khi hiệu suất là mối quan tâm lớn hơn bộ nhớ và trừu tượng.
Michael Scheper
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.