Java'da bir listeyi ters sırada yineleme


251

Jenerikler kullanmak için bir kod parçası geçiriyorum. Bunu yapmak için bir argüman, for döngüsünün dizinleri takip etmekten veya açık bir yineleyici kullanmaktan çok daha temiz olmasıdır.

Vakaların yaklaşık yarısında, liste (ArrayList) bugün bir dizin kullanılarak ters sırada yineleniyor.

Birisi bunu yapmak için daha temiz bir yol önerebilir ( indexed for loopkoleksiyonlarla çalışırken ne zaman sevmediğimden ), ancak işe yarıyor mu?

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

Not: JDK dışında yeni bağımlılıklar ekleyemiyorum.


10
Dizinlenmiş veri yapısı üzerinden yineleme yapmak için açık bir dizin kullanma konusunda bu kadar kötü olan nedir? En azından tam olarak neler olduğunu gösteriyor. Geriye doğru yineleme için her zaman aşağıdaki biraz daha kısa deyim:for (int i = nodes.size(); --i >= 0;)
x4u

4
Özellikle bir şey değil, sadece bir arayüze programlamayı tercih ediyorum ve ne tür bir liste kullandığımı bilmiyorum. Kısa elinizi çok sevmeme rağmen. (+1 yorum)
Allain Lalonde

1
@ x4u: Yineleyici hata hızında olmasına ve yineleme sırasında öğelerin kolayca kaldırılmasına izin verse de çok fazla şey yok.
Adamski

2
Bu sınıf bozulur, çünkü kullanıcı aynı Yinelenebilir değer üzerinden ikinci kez yineleme yapmak isteyebilir veya liste yinelemenin oluşturulduğu zaman ile yinelendiği zaman arasında değişebilir. Emin değilim sizin amaçlarınız için bunu yapmak emin değilsiniz, ama kodu düzeltmek için çok zor olmaz; ya da sadece kodu
Guava'dan çalın

1
Yeterince adil, ama doğru okuduğumda Guava bile aynı şeye duyarlıdır. Bir kullanıcı sonucun bir kopyasını tersinden saklasaydı, aynı sorunla karşılaşırdı.
Allain Lalonde

Yanıtlar:


447

Bunu dene:

// 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
Fena değil. Dizini kullanmaz, ancak her sözdiziminin zarafetini kaybeder. Yine de +1.
Allain Lalonde

26
Dizin bağımsız değişkeni olmadan listIterator () öğesini çağırmak listenin başında bir Yineleyici verir ve böylece hasPrevious () ilk çağrıda false değerini döndürür.
Adamski

2
listIteratorÇağrıda bir dizin istiyorsun , sanırım.
Tom Hawtin - tackline

1
Tersine Iteratorbir kullanan bir yazabilirsiniz ListIterator, ancak bir döngü için buna değmeyebilir.
Tom Hawtin - tackline

1
Bu bir döngü değil, bu yüzden aldım ve sardım. pastebin.ca/1759041 , şimdi yapabilirimfor (Node each : new ListReverse<Node>(nodes)) { }
Allain Lalonde

35

Guava teklifleri Lists#reverse(List)ve ImmutableList#reverse(). Guava için çoğu durumda olduğu gibi, argüman birImmutableList , böylece her durumda ilkini kullanabilirsiniz. Bunlar listenin yeni kopyalarını oluşturmaz, sadece listenin "ters çevrilmiş görünümlerini" oluşturur.

Misal

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

23

Ben for döngüsü sözdizimini kullanarak mümkün olduğunu sanmıyorum. Önerebileceğim tek şey şöyle bir şey yapmaktır:

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

... ama daha az verimli olacağı düşünüldüğünde bunun "daha temiz" olduğunu söyleyemem.


26
ve ayrıca geçtiğiniz listeyi değiştirir, bu da oldukça büyük bir yan etkidir. (Bunu bir yönteme
sardığınızı varsayalım

Bu en temiz çözüm.
jfajunior

14

Seçenek 1: Listeyi Koleksiyonlar # reverse () ile ters çevirip foreach kullanmayı düşündünüz mü ?

Tabii ki, kodunuzu liste doğru sıralanacak şekilde yeniden düzenlemek de isteyebilirsiniz, böylece geri çevirmek zorunda kalmazsınız, bu da ekstra alan / zaman kullanır.


DÜZENLE:

Seçenek 2: Alternatif olarak, ArrayList yerine Deque kullanabilir misiniz ? İleri ve geri yinelemenize izin verecektir


DÜZENLE:

Seçenek 3: Diğerlerinin önerdiği gibi, listeyi tersine çevirecek bir Yineleyici yazabilirsiniz, işte bir örnek:

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
Bunu düşündüm, ama tersine çevirmenin maliyeti yasak. Ayrıca, bu tür bir yinelemenin yaklaşık yarısını gerektirir. Bunu çevirmek sorunu sadece diğer yarısına taşıyacaktır.
Allain Lalonde

3
Deque için +; tipik uygulamalara sahiptir descendingIterator().
trashgod

Bu, bağlantılı listeler için korkunç bir performans sergileyecek, OP'nin varyantı daha iyi.
masterxilo

1
for eachBence bir ifade kullanmak en deyimsel çözümdür. Listeniz geriye doğru yinelenecek şekilde Yinelenebilir bir uygulama uyguluyorsa bunun mümkün olduğunu anlamak güzel. Bu yaklaşımı kullanacağım ve Apache Commons Koleksiyonlarından ReverseListIterator sınıfını kullanacağım.
David Groomes

11

LinkedListGenel arabirim yerine somut sınıfı kullanabilirsiniz List. Sonra descendingIteratorters yönde yineleme için bir var .

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

Hiçbir yoktur Neden bilmiyorum descendingIteratorile ArrayList...


9

Bu eski bir soru, ama java8 dostu bir cevaptan yoksun. Akış API'sının yardımıyla listeyi tersine çevirmenin bazı yolları şunlardır:

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
çok güzel, sadece kozmetik bir değişiklik: int size = list.size (); ListIterator <Integer> it = list.listIterator (boyut); Stream.generate (o :: önceki) .limit (boyut) .forEach (System.out :: println);
benez

5

İşte bir (denenmemiş) uygulaması ReverseIterable. Ne zaman iterator()denir yarattığı ve özel döndürür ReverseIteratorbasitçe çağrıları eşleyen uygulanmasını hasNext()etmek hasPrevious()ve bu aramaları next()eşlenir previous(). Bu ArrayList, tersi yönde aşağıdaki gibi tekrarlayabileceğiniz anlamına gelir :

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

Sınıf Tanımı

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

Herkese açık bir yapıcı yerine statik bir yöntem olarak açığa çıkmasına rağmen (çoğu durumda) istemcinin type parametresini belirtmesi gereğini ortadan kaldırır. Guava böyle yapar ..
Kevin Bourrillion

2
Bu en iyi uygulama, ancak ReverseIteratorgerekli yapıcı eksik ve kod Listyerine kullanmanız gerekir ArrayList.
Andy

5

Listeler, performansın gerçek bir sorun olmaması için oldukça küçükse, reverse-class Listssınıfının -metod'u kullanılabilir Google Guava. Pretty for-each-code verir ve orijinal liste aynı kalır. Ayrıca, tersine çevrilmiş liste orijinal liste tarafından desteklenir, bu nedenle orijinal listede yapılacak herhangi bir değişiklik ters çevrilmiş listeye yansıtılacaktır.

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

Aşağıdaki sonucu verir:

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

Bu, myList'in ters yinelemesinin şu şekilde yazılabileceği anlamına gelir:

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

5

Bir özel oluşturun reverseIterable.


Bunun neden oylandığını bilmiyorum, sadece bunu yapan liste için bir sarıcı yapabilirim.
Allain Lalonde

Bu, Collections.reverse () IMHO öğesini çağırmaktan daha az temiz değildir.
Adamski

@Allain: Yeniden kabul etti. listIterator (list.size ()) çağrısını neden "temiz değil" olarak görüntülediğinizi görmüyorum. Sarsanız bile, aynı yöntemi bir yere çağırmanız gerekiyor.
Adamski

Temizlik için performans vuruşunu yapmaya istekli olmadığınızı varsayalım.
Allain Lalonde

18
Bu tam bir cevap olarak düşünmüyorum gibi oy verildi.
Maarten Bodewes

3

Çok basit bir örnek:

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


2

Ayrıca Google koleksiyonları ters yöntemi bulundu .


Google koleksiyonunun hangi sürümü? bağlantınız artık mevcut değil.
AaA

1

Şuna benzeyen bir koda sahip olmak için:

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

Bu kodu "In.java" adlı bir dosyaya yerleştirin:

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
Bu, iş parçacığının başka bir yerinde belirtilmiştir, ancak listIteratoralanın Iteratoruygulama içinde değil uygulama içinde olması gerekir Iterable.
Andy

1
Neden sınıf yerine enum türü kullanıyorsunuz?
Aubin

1
'In' sınıfını, özel bir varsayılan kurucu yazmak zorunda kalmadan, somutlaştırılamaz yapar. Yalnızca statik yöntemleri olan sınıflar için iyidir.
intrepidis

Özel bir varsayılan kurucu yazmaktan kaçınmak için enum'ları kötüye kullanmak en iyi ihtimalle kafa karıştırıcı olduğunu söyleyebilirim. Numaralandırmalar için numaralandırmalar ve sınıflar için sınıflar kullanmaya ne dersiniz? Bir sayıyı bir sınıf haline getirerek , örneğin bir name()yöntemi, bir ordinal()yöntemi ve bir static valueOf()yöntemi de dolaylı olarak alır .
JHH

1
Evet, görüşünüze devam edebilirsiniz ve bu da işe yarayacaktır, ancak tam tersini düşünüyorum. Sınıflar, nesneleri örneklemek ve örneklemelerini engellemek için özel bir varsayılan kurucu ekleyerek onları kötüye kullanmak içindir. Java'da numaralandırmalar aslında sınıflardır, ancak tasarımla hiçbir zaman örneklenemeyecek olanlar.
intrepidis

0

En az iki kez önerildiği gibi, descendingIteratora ile Deque, özellikle a ile kullanabilirsiniz LinkedList. For-each döngüsünü kullanmak istiyorsanız (yani, an var Iterable), şöyle bir sarmalayıcı oluşturabilir ve kullanabilirsiniz:

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

Sebep: "ArrayList ile neden descendingIterator olmadığını bilmiyorum ..."

Dizi listesi listeye veriler eklendiği sırada listeyi tutmadığından. Yani, asla Arraylist kullanmayın.

Bağlantılı liste, verileri listeye EKLE aynı sırada tutar.

Yani, benim örneğimde, kullanıcının fikrini bükmek ve yanlarından bir şey egzersiz yapmak için ArrayList () kullandım.

Bunun yerine

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

KULLANIM:

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
"Dizi listesi listeye veri eklenmesiyle aynı sırada olmadığından" umm, evet öyle, en azından bağlantılı listeyle aynı şekilde. Nasıl yapmadıklarına bir örnek verebilir misiniz?
corsiKa

Evet, ArrayList ve LinkedList (Genel olarak Liste sözleşmesi) öğelerin sırada eklenmesini sağlar. Set sözleşmesi sıralamaya göre sıralanmamış veya sıralanmıştır (TreeSet). Ters fikir kötü değil, ama aslında yavaşlayabilecek listeyi yeniden sıraladığını unutmayın.
Bill K

2
ArrayLists'in öğeleri ekleme düzeninde tutmadıklarını yanlışlıkla belirttiği için bu cevabı iptal ettim. @ Bill-k notlarında olduğu gibi, tüm Listeler tanımlarına göre sıralı koleksiyonlardır. Bir LinkedList kullanma önerisi, descendingIterator () yöntemini içerdiğinden, ancak performans yalnızca bellek ve soyutlamadan daha büyük bir endişe içeriyorsa, liyakate sahiptir.
Michael Scheper
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.