วนซ้ำรายการในลำดับย้อนกลับใน java


251

ฉันกำลังโยกย้ายรหัสเพื่อใช้ประโยชน์จาก Generics อาร์กิวเมนต์หนึ่งสำหรับการทำเช่นนั้นคือ for for loop นั้นสะอาดกว่าการติดตามดัชนีหรือใช้ตัววนซ้ำอย่างชัดเจน

ในกรณีประมาณครึ่งหนึ่งรายการ (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 เดียวกันหรือรายการอาจเปลี่ยนไปเมื่อสร้าง iterable และสร้างซ้ำ ฉันแน่ใจว่าสำหรับวัตถุประสงค์ของคุณคุณจะต้องไม่ทำอย่างนั้น แต่มันจะไม่ยากเกินไปที่จะแก้ไขรหัส; หรือเพียงขโมยรหัสจาก Guava (ใบอนุญาต Apache 2.0): code.google.com/p/guava-l
ไลบรารี/source/browse/trunk/src/com/…

1
ยุติธรรมเพียงพอ แต่แม้แต่ฝรั่งก็ยังอ่อนไหวกับสิ่งเดียวกันหากฉันอ่านถูก เป็นผู้ใช้ที่จะเก็บสำเนาผลย้อนกลับก็มีปัญหาเดียวกัน
Allain Lalonde

คำตอบ:


447

ลองสิ่งนี้:

// 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 () โดยไม่มีอากิวเมนต์ index จะให้ 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()ในกรณีส่วนใหญ่สำหรับ Guava ผู้ได้รับมอบหมายคนก่อนจะเป็นฝ่ายหลังหากข้อโต้แย้งเป็นImmutableListดังนั้นคุณสามารถใช้แบบดั้งเดิมในทุกกรณี สิ่งเหล่านี้จะไม่สร้างสำเนาใหม่ของรายการ แต่เพียงแค่ "มุมมองย้อนกลับ" ของรายการ

ตัวอย่าง

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

23

ฉันไม่คิดว่าเป็นไปได้ที่จะใช้สำหรับไวยากรณ์วนรอบ สิ่งเดียวที่ฉันแนะนำได้คือทำสิ่งที่ชอบ:

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

... แต่ฉันจะไม่บอกว่านี่คือ "สะอาด" เนื่องจากมันจะมีประสิทธิภาพน้อยลง


26
และยังเปลี่ยนแปลงรายการที่คุณสำรวจซึ่งค่อนข้างมีผลข้างเคียงมาก (สมมติว่าคุณห่อสิ่งนี้ด้วยวิธีการแต่ละครั้งมันถูกเรียกว่าคุณสำรวจรายการด้วยวิธีอื่น ^^)
jolivier

นี่คือทางออกที่สะอาดที่สุด
jfajunior

14

ตัวเลือกที่ 1: คุณคิดที่จะย้อนกลับรายการด้วยชุด # ย้อนกลับ ()แล้วใช้ foreach หรือไม่

แน่นอนคุณอาจต้องการ refactor รหัสของคุณเพื่อให้รายการถูกต้องดังนั้นคุณไม่จำเป็นต้องย้อนกลับซึ่งใช้พื้นที่ / เวลาเพิ่มเติม


แก้ไข:

ตัวเลือกที่ 2: อีกวิธีหนึ่งคุณสามารถใช้Dequeแทน ArrayList ได้หรือไม่ มันจะช่วยให้คุณย้ำไปข้างหน้าและข้างหลัง


แก้ไข:

ตัวเลือก 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นิพจน์เป็นวิธีแก้ปัญหาที่สำนวนที่สุดในความคิดของฉัน เป็นเรื่องดีที่ได้ทราบว่าสิ่งนี้เป็นไปได้หากรายการของคุณใช้ Iterable ในแบบวนซ้ำไปข้างหลัง ฉันจะใช้วิธีนี้และใช้คลาส ReverseListIterator จาก Apache Commons Collections
David Groomes

11

คุณสามารถใช้ชั้นคอนกรีตแทนของอินเตอร์เฟซทั่วไปLinkedList Listจากนั้นคุณมีการdescendingIteratorวนซ้ำสำหรับทิศทางย้อนกลับ

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

ไม่ทราบว่าทำไมไม่มีdescendingIteratorกับArrayList...


9

นี่เป็นคำถามเก่า แต่ไม่มีคำตอบที่เป็นมิตรกับ 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

2
เยี่ยมมากเพียงเปลี่ยนเครื่องสำอาง: int size = list.size (); ListIterator <Integer> it = list.listIterator (ขนาด); Stream.generate (มัน :: ก่อนหน้า) จำกัด การ (ขนาด) .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()));
  }
}

ดูถูกต้องสำหรับฉันแม้ว่าการเปิดเผยว่าเป็นวิธีการแบบคงที่แทนที่จะเป็นตัวสร้างสาธารณะจะ (ในกรณีส่วนใหญ่) หลีกเลี่ยงความต้องการของไคลเอนต์เพื่อระบุพารามิเตอร์ชนิด นี่เป็นวิธีที่ Guava ทำ ..
Kevin Bourrillion

2
นี่คือการดำเนินงานที่ดีที่สุด แต่ReverseIteratorจะหายไปตัวสร้างรหัสที่จำเป็นและควรใช้แทนList ArrayList
แอนดี้

5

หากรายการมีขนาดค่อนข้างเล็กเพื่อให้ประสิทธิภาพไม่ใช่ปัญหาจริงคุณสามารถใช้reverse-metod ของLists-class in Google Guavaได้ ผลตอบแทน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
}

5

reverseIterableสร้างที่กำหนดเอง


ไม่ทราบว่าทำไมสิ่งนี้ถึงลงคะแนนเสียงฉันอาจทำเสื้อคลุมสำหรับรายการที่ทำสิ่งนี้
Allain Lalonde

นี่ไม่น้อยไปกว่าการเรียก Collections.reverse () IMHO
Adamski

@ Allain: เห็นด้วยอีกครั้ง downvotes แม้ว่าฉันจะไม่เห็นสาเหตุที่คุณดูการเรียกไปยัง listIterator (list.size ()) เป็น "not clean" แม้ว่าคุณจะหุ้มมันคุณยังคงต้องใช้วิธีการเดียวกันเรียกบางแห่ง
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การนำไปใช้
แอนดี้

1
เหตุใดจึงใช้ชนิด enum ไม่ใช่คลาส?
Aubin

1
มันทำให้คลาส 'In' ไม่สามารถใช้งานได้ทันทีโดยไม่ต้องเขียน Constructor เริ่มต้นส่วนตัว เหมาะสำหรับคลาสที่มีเมธอดแบบสแตติกเท่านั้น
intrepidis

ฉันจะบอกว่าการใช้ enums ในทางที่ผิดเพื่อหลีกเลี่ยงการเขียนคอนสตรัคเตอร์เริ่มต้นส่วนตัวนั้นทำให้เกิดความสับสน วิธีการเกี่ยวกับการใช้ enums สำหรับ enums และชั้นเรียนสำหรับชั้นเรียน? โดยการทำให้คลาสเป็น enum มันยังได้รับname()เมธอด, ordinal()เมธอดและstatic valueOf()เมธอดตัวอย่างเช่น
JHH

1
ใช่คุณสามารถดำเนินการต่อกับความคิดเห็นของคุณและที่จะทำงานได้ดีเช่นกัน แต่ฉันคิดว่าตรงกันข้าม คลาสสำหรับการสร้างอินสแตนซ์ออบเจ็กต์และเหยียดหยามพวกมันโดยการรวมคอนสตรัคเตอร์เริ่มต้นส่วนตัวเพื่อยับยั้งอินสแตนซ์ของพวกเขาสับสนที่ดีที่สุด ใน Java, enums จริง ๆ แล้วเป็นคลาส แต่คลาสที่ไม่สามารถสร้างอินสแตนซ์ได้โดยการออกแบบ
intrepidis

0

ในฐานะที่ได้รับการแนะนำอย่างน้อยสองครั้งคุณสามารถใช้descendingIteratorด้วยโดยเฉพาะอย่างยิ่งกับDeque LinkedListหากคุณต้องการใช้ for-each loop (เช่นมีIterable) คุณสามารถสร้างและใช้ wraper ได้ดังนี้

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 (รายการสัญญาโดยทั่วไป) เก็บรายการไว้ในลำดับ สัญญาชุดนั้นไม่ได้เรียงลำดับหรือเรียงลำดับตาม (ชุดต้นไม้) แนวคิดย้อนกลับไม่เลว แต่จำไว้ว่าจริง ๆ แล้วเรียงลำดับรายการที่อาจช้า
Bill K

2
ฉันลงคะแนนคำตอบนี้เพราะระบุว่า ArrayLists ไม่ได้เก็บรายการตามลำดับการแทรก ในฐานะที่เป็น @ Bill-k บันทึกรายการทั้งหมดได้รับคำสั่งคอลเลกชันตามคำนิยาม ข้อเสนอแนะในการใช้ LinkedList แทนมีข้อดีเนื่องจากมีวิธีการ descendingIterator () แต่ถ้าประสิทธิภาพมีความกังวลมากกว่าหน่วยความจำและนามธรรม
Michael Scheper
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.