Java에서 목록을 역순으로 반복


251

제네릭을 사용하기 위해 코드 조각을 마이그레이션하고 있습니다. 그렇게하는 한 가지 주장은 for 루프가 인덱스를 추적하거나 명시적인 반복자를 사용하는 것보다 훨씬 깨끗하다는 것입니다.

약 절반의 경우 오늘 목록을 사용하여 목록 (ArrayList)이 역순으로 반복됩니다.

누군가 indexed for loop가 컬렉션을 사용할 때 싫어하기 때문에이 작업을 수행하는 더 깨끗한 방법을 제안 할 수 있습니까 ?

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

참고 : JDK 외부에 새로운 종속성을 추가 할 수 없습니다.


10
명시 적 인덱스를 사용하여 인덱스 된 데이터 구조를 반복하는 데있어 나쁜 점은 무엇입니까? 적어도 정확히 무슨 일이 일어나고 있는지 보여줍니다. 거꾸로 반복하기 위해 나는 항상 다음과 같은 약간 짧은 관용구 :for (int i = nodes.size(); --i >= 0;)
x4u

4
특히, 나는 단지 인터페이스로 프로그래밍하고 어떤 종류의 목록을 사용하고 있는지 알지 못합니다. 나는 당신의 짧은 손을 크게 좋아하지만. (+1 코멘트)
Allain Lalonde

1
@ x4u : Iterator는 실패 속도가 빠르지 만 반복 중에 요소를 쉽게 제거 할 수는 있지만 그다지 많지는 않습니다.
Adamski

2
이 클래스는 사용자가 동일한 Iterable에 대해 두 번째 반복을 원하거나 반복 가능 구성 시간과 반복 가능 시간 사이에 목록이 변경 될 수 있기 때문에 중단되었습니다. 나는 당신의 목적을 위해 그것을하지 않을 것이라고 확신하지만 코드를 수정하는 것은 너무 어렵지 않을 것입니다. 또는 구아바에서 코드를 도용 (아파치 2.0 라이센스) : code.google.com/p/guava-libraries/source/browse/trunk/src/com/…
Kevin Bourrillion

1
충분히 공평하지만 구아바 하나조차도 올바르게 읽으면 같은 일에 취약합니다. 결과의 사본을 역순으로 유지해야하는 사용자에게는 동일한 문제가 있습니다.
Allain Lalonde

답변:


446

이 시도:

// 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
나쁘지 않다. 색인을 사용하지 않지만 각 구문의 우아함을 잃습니다. 어쨌든 +1.
Allain Lalonde

26
인덱스 인수없이 listIterator ()를 호출하면 목록의 시작 부분에 Iterator가 제공되므로 hasPrevious ()는 첫 번째 호출에서 false를 리턴합니다.
Adamski

2
당신은 listIterator전화에 색인을 원한다고 생각합니다.
Tom Hawtin-tackline

1
Iterator을 사용하는를 작성할 수는 ListIterator있지만 하나의 루프에는 가치가 없습니다.
Tom Hawtin-tackline

1
그것은 하나의 루프가 아니므로 이것을 가져 와서 포장했습니다. pastebin.ca/1759041 , 이제 할 수 있습니다for (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde

35

구아바 제공 Lists#reverse(List)하고 ImmutableList#reverse(). 구아바의 대부분의 경우와 마찬가지로, 인수가이면 전자가 후자를 위임 ImmutableList하므로 모든 경우에 전자를 사용할 수 있습니다. 이들은리스트의 새로운 사본을 생성하지 않고 단지 "역전 된 뷰"를 생성합니다.

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

23

for 루프 구문을 사용하는 것이 가능하다고 생각하지 않습니다. 내가 제안 할 수있는 유일한 것은 다음과 같은 일을하는 것입니다.

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

...하지만 효율성이 떨어질 것이므로 이것이 더 깨끗하다고는 말할 수 없습니다.


26
트래버스 목록의 위치가 변경되어 큰 부작용이 발생합니다. (메서드를 다른 방법으로 목록이라고 탐색 할 때마다 이것을 메소드로 랩한다고 ^^)
jolivier

이것이 가장 깨끗한 솔루션입니다.
jfajunior

14

옵션 1 : Collections # reverse ()를 사용 하여 목록을 뒤집는 것에 대해 생각해 보셨습니까? foreach를 사용 ?

물론 목록을 올바르게 정렬하도록 코드를 리팩터링하여 역 / 시간을 추가로 사용하지 않아도되도록 할 수 있습니다.


편집하다:

옵션 2 : 또는 ArrayList 대신 Deque 를 사용할 수 있습니까? 앞뒤로 반복 할 수 있습니다.


편집하다:

옵션 3 : 다른 사람들이 제안했듯이 목록을 거꾸로 반복하는 Iterator를 작성할 수 있습니다. 예는 다음과 같습니다.

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
그것에 대해 생각했지만 그것을 뒤집는 데 드는 비용은 엄청납니다. 또한 절반 정도의 시간이 소요됩니다. 뒤집 으면 문제가 다른 절반으로 이동합니다.
Allain Lalonde

3
+ Deque; 일반적인 구현에는 descendingIterator().
trashgod

이것은 연결된 목록에서 끔찍한 성능을 발휘할 수 있으며 OP의 변형이 더 좋습니다.
masterxilo

1
for each내 의견으로 는 표현을 사용하는 것이 관용적 인 해결책입니다. List가 거꾸로 반복하는 방식으로 Iterable을 구현하면 이것이 가능하다는 것을 알고 있습니다. 이 방법을 사용하고 Apache Commons Collections의 ReverseListIterator 클래스를 사용하겠습니다.
David Groomes

11

LinkedList일반 인터페이스 대신 구체적 클래스 를 사용할 수 있습니다 List. 그런 다음 descendingIterator반대 방향으로 반복 할 수 있습니다 .

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

어떤이 이유를 알고하지 마십시오 descendingIteratorArrayList...


9

이것은 오래된 질문이지만 java8 친화적 인 답변이 부족합니다. 다음은 스트리밍 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

2
아주 좋은, 단지 외관상의 변화 : int size = list.size (); ListIterator <정수> it = list.listIterator (size); Stream.generate (it :: previous) .limit (size) .forEach (System.out :: println);
benez

5

다음은 (의 평가되지 않은) 구현입니다 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()));
  }
}

공개 생성자 대신 정적 메소드로 노출하면 클라이언트가 유형 매개 변수를 지정할 필요가 없습니다. 이 구아바 그것을 수행하는 방법입니다 ..
케빈 Bourrillion

2
이것이 가장 좋은 구현이지만 ReverseIterator필요한 생성자가 누락되어 코드 List대신에 사용해야 합니다 ArrayList.
Andy

5

성능이 실제 문제가되지 않도록 목록이 상당히 작은 reverse경우에 Lists-class 의 -metod를 사용할 수 있습니다 Google Guava. 예쁜 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
}

5

사용자 정의를 만듭니다 reverseIterable.


왜 이것이 투표에 실패했는지 모릅니다.이 작업을 수행하는 목록에 대한 래퍼를 만들 수도 있습니다.
Allain Lalonde

Collections.reverse () IMHO를 호출하는 것보다 덜 깨끗합니다.
Adamski

@Allain : 다시 합의했습니다. downvotes 비록 listIterator (list.size ()) 호출을 "정리되지 않음"으로 보는 이유는 모르겠습니다. 그것을 감쌀지라도 여전히 어딘가에 같은 메소드 호출을해야합니다.
Adamski

청결을 위해 성능 저하를 기꺼이 감수하지 않는다고 가정하십시오.
Allain Lalonde

18
나는 이것이 완전한 대답이라고 생각하지 않는다고 투표했다.
Maarten Bodewes

3

매우 간단한 예 :

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

다음과 같은 코드를 만들려면

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

1
이것은 스레드의 다른 곳에서 언급되었지만 listIterator필드는 Iterator구현이 아닌 구현 내부에 있어야합니다 Iterable.
Andy

1
왜 클래스가 아닌 열거 형을 사용합니까?
Aubin

1
전용 기본 생성자를 작성할 필요없이 'In'클래스를 인스턴스화 할 수 없습니다. 정적 메서드 만있는 클래스에 좋습니다.
intrepidis

개인 기본 생성자를 작성하지 않기 위해 열거 형을 남용하는 것이 혼란 스럽습니다. 열거 형에 열거 형을 사용하고 클래스에 클래스를 사용하는 것은 어떻습니까? 클래스를 열거 형으로 만들면 예를 들어 메서드, 메서드 및 name()메서드를 암시 적으로 가져옵니다 . ordinal()static valueOf()
JHH

1
예, 당신은 당신의 의견을 계속할 수 있고 그것도 잘 작동 할 것이지만, 나는 반대로 생각합니다. 클래스는 객체를 인스턴스화하고 개인 기본 생성자를 포함하여 인스턴스화를 방해하여 인스턴스화를 방해하는 것이 최선의 방법입니다. Java에서 열거 형은 실제로 클래스이지만 디자인으로 인스턴스화 할 수없는 클래스입니다.
intrepidis

0

적어도 두 번 제안 된 것처럼 , 특히 descendingIteratora와 함께 사용할 수 있습니다 . for-each 루프를 사용하려면 (예 :) 다음과 같이 래퍼를 구성하고 사용할 수 있습니다.DequeLinkedListIterable

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로 내림차순 반복자가없는 이유를 모르겠습니다 ..."

배열 목록은 목록에 데이터가 추가 된 것과 동일한 순서로 목록을 유지하지 않습니다. 따라서 절대 Arraylist를 사용하지 마십시오.

연결된 목록은 목록에 ADD와 동일한 순서로 데이터를 유지합니다.

따라서 위의 예제에서 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) {
  ...
}

4
"배열 목록은 목록에 데이터가 추가 된 것과 같은 순서로 목록을 유지하지 않기 때문에"그렇습니다. 적어도 연결된 목록과 같은 방식으로 수행됩니다. 그들이하지 않는 방법의 예를 제공 할 수 있습니까?
corsiKa

예. ArrayList 및 LinkedList (일반적으로 List 계약)는 항목을 순서대로 삽입합니다. 세트 계약이 정렬되지 않았거나 정렬 (TreeSet)별로 정렬되었습니다. 반대 생각은 나쁘지 않지만 실제로 목록을 재정렬하여 느려질 수 있음을 기억하십시오.
Bill K

2
ArrayLists가 항목을 삽입 순서로 유지하지 않는다고 잘못 설명했기 때문에이 답변을 하향 투표했습니다. @ bill-k 메모와 같이 모든 목록은 정의에 따라 모음으로 정렬됩니다. LinkedList를 사용하는 제안은 내려가는 Iterator () 메소드를 갖추고 있기 때문에 성능이 메모리와 추상화보다 더 큰 관심사 인 경우에만 장점이 있습니다.
Michael Scheper 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.