รับองค์ประกอบสุดท้ายของสตรีม / รายการในซับเดียว


119

ฉันจะรับองค์ประกอบสุดท้ายของสตรีมหรือรายการในรหัสต่อไปนี้ได้อย่างไร

ที่ไหนdata.careasเป็นList<CArea>:

CArea first = data.careas.stream()
                  .filter(c -> c.bbox.orientationHorizontal).findFirst().get();

CArea last = data.careas.stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .collect(Collectors.toList()).; //how to?

อย่างที่คุณเห็นการรับองค์ประกอบแรกด้วยองค์ประกอบบางอย่างfilterไม่ใช่เรื่องยาก

อย่างไรก็ตามการได้รับองค์ประกอบสุดท้ายในหนึ่งซับเป็นความเจ็บปวดอย่างแท้จริง:

  • ดูเหมือนว่าฉันไม่สามารถรับได้โดยตรงจากไฟล์Stream. (มันจะสมเหตุสมผลสำหรับสตรีมที่ จำกัด เท่านั้น)
  • ดูเหมือนว่าคุณไม่สามารถรับสิ่งต่างๆเช่นfirst()และlast()จากListอินเทอร์เฟซได้ซึ่งเป็นความเจ็บปวดจริงๆ

ฉันไม่เห็นอาร์กิวเมนต์ใด ๆ ที่ไม่ให้ a first()และlast()method ในListอินเทอร์เฟซเนื่องจากองค์ประกอบในนั้นได้รับคำสั่งและยิ่งกว่านั้นขนาดก็เป็นที่รู้จัก

แต่ตามคำตอบเดิม: จะได้รับองค์ประกอบสุดท้ายของ จำกัด ได้Streamอย่างไร?

โดยส่วนตัวแล้วนี่เป็นสิ่งที่ใกล้เคียงที่สุดที่ฉันจะได้รับ:

int lastIndex = data.careas.stream()
        .filter(c -> c.bbox.orientationHorizontal)
        .mapToInt(c -> data.careas.indexOf(c)).max().getAsInt();
CArea last = data.careas.get(lastIndex);

อย่างไรก็ตามมันเกี่ยวข้องกับการใช้indexOfทุกองค์ประกอบซึ่งโดยทั่วไปแล้วคุณไม่ต้องการเนื่องจากอาจทำให้ประสิทธิภาพลดลง


10
ฝรั่งให้Iterables.getLastซึ่งใช้เวลา Iterable Listแต่มีการเพิ่มประสิทธิภาพในการทำงานด้วย getFirstโกรธสัตว์เลี้ยงก็คือว่ามันไม่ได้มี โดยStreamทั่วไป API เป็นแบบทวารหนักอย่างน่ากลัวโดยไม่ต้องใช้วิธีอำนวยความสะดวกมากมาย โดย LINQ ของ C # โดย constrast ยินดีที่จะให้.Last()และ.Last(Func<T,Boolean> predicate)แม้ว่าจะรองรับการนับจำนวนไม่สิ้นสุดด้วย
Aleksandr Dubinsky

@AleksandrDubinsky upvoted แต่หนึ่งบันทึกสำหรับผู้อ่าน StreamAPI ไม่สามารถเทียบเคียงได้อย่างสมบูรณ์LINQเนื่องจากทั้งสองทำในกระบวนทัศน์ที่แตกต่างกันมาก มันไม่ได้แย่ลงหรือดีขึ้นเพียง แต่แตกต่างกัน และแน่นอนว่าไม่มีวิธีการบางอย่างเพราะ oracle devs ไร้ความสามารถหรือปานกลาง :)
fasth

1
สำหรับซับเดียวจริงเธรดนี้อาจใช้งานได้
quantum

คำตอบ:


186

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

Stream<T> stream = ...; // sequential or parallel stream
Optional<T> last = stream.reduce((first, second) -> second);

การใช้งานนี้ใช้ได้กับสตรีมที่สั่งซื้อทั้งหมด(รวมถึงสตรีมที่สร้างจากรายการ ) สำหรับสตรีมที่ไม่เรียงลำดับจะมีเหตุผลที่ชัดเจนโดยไม่ได้ระบุว่าจะส่งคืนองค์ประกอบใด

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

  • Javadoc สำหรับวิธีการสตรีม :: ลดรัฐว่า"จะไม่จำกัด ในการดำเนินการตามลำดับ "
  • Javadoc ยังกำหนดให้"ฟังก์ชันตัวสะสมต้องเป็นฟังก์ชันที่เชื่อมโยงกันไม่รบกวนและไม่มีสถานะสำหรับการรวมค่าสองค่า"ซึ่งเห็นได้ชัดว่าเป็นกรณีของนิพจน์แลมบ์ดา(first, second) -> secondซึ่งจะเห็นได้ชัดในกรณีของการแสดงออกแลมบ์ดา
  • Javadoc สำหรับการดำเนินการลดสถานะ: "คลาสสตรีมมีหลายรูปแบบของการดำเนินการลดทั่วไปเรียกว่าลด ()และรวบรวม () [.. ]"และ"การดำเนินการลดที่สร้างขึ้นอย่างเหมาะสมสามารถขนานกันได้โดยเนื้อแท้ตราบใดที่ฟังก์ชัน (s ) ที่ใช้ในการประมวลผลองค์ประกอบมีความเชื่อมโยงและไร้สัญชาติ "

เอกสารสำหรับนักสะสมที่เกี่ยวข้องอย่างใกล้ชิดนั้นชัดเจนยิ่งขึ้น: "เพื่อให้แน่ใจว่าการดำเนินการตามลำดับและแบบขนานจะให้ผลลัพธ์ที่เท่าเทียมกันฟังก์ชันตัวรวบรวมจะต้องเป็นไปตามเอกลักษณ์และข้อ จำกัด ในการเชื่อมโยง "


กลับไปที่คำถามเดิม: โค้ดต่อไปนี้จัดเก็บการอ้างอิงถึงองค์ประกอบสุดท้ายในตัวแปรlastและจะแสดงข้อยกเว้นหากสตรีมว่างเปล่า ความซับซ้อนเป็นเส้นตรงตามความยาวของสตรีม

CArea last = data.careas
                 .stream()
                 .filter(c -> c.bbox.orientationHorizontal)
                 .reduce((first, second) -> second).get();

ดีมากขอบคุณ! คุณทราบหรือไม่ว่าอาจจะละชื่อ (อาจใช้_หรือคล้ายกัน) ในกรณีที่คุณไม่ต้องการพารามิเตอร์ ดังนั้นจะเป็น: .reduce((_, current) -> current)ถ้าไวยากรณ์ที่ถูกต้องเท่านั้น
skiwi

2
@skiwi คุณสามารถใช้ชื่อตัวแปรทางกฎหมายใด ๆ ก็ได้เช่น.reduce(($, current) -> current)หรือ.reduce((__, current) -> current)(ขีดล่างคู่)
assylias

2
ในทางเทคนิคอาจใช้ไม่ได้กับสตรีมใด ๆ เอกสารที่คุณชี้ไปและStream.reduce(BinaryOperator<T>)ไม่มีการพูดถึงหากreduceปฏิบัติตามคำสั่งพบและการดำเนินการเทอร์มินัลมีอิสระที่จะเพิกเฉยต่อลำดับการเผชิญหน้าแม้ว่าจะมีการสั่งสตรีมก็ตาม นอกจากนี้คำว่า "commutative" จะไม่ปรากฏใน Stream javadocs ดังนั้นการไม่มีอยู่จึงไม่ได้บอกอะไรเรามากนัก
Aleksandr Dubinsky

2
@AleksandrDubinsky: แน่นอนว่าเอกสารไม่ได้กล่าวถึงการสับเปลี่ยนเนื่องจากไม่เกี่ยวข้องกับการดำเนินการลด ส่วนที่สำคัญคือ: "[.. ] การดำเนินการลดที่สร้างขึ้นอย่างเหมาะสมสามารถขนานกันได้โดยเนื้อแท้ตราบเท่าที่ฟังก์ชันที่ใช้ในการประมวลผลองค์ประกอบนั้นเชื่อมโยงกัน [.. ]"
nosid

2
@Aleksandr Dubinsky: แน่นอนว่ามันไม่ใช่“ คำถามเชิงทฤษฎีของข้อมูลจำเพาะ” มันสร้างความแตกต่างระหว่างreduce((a,b)->b)การเป็นโซลูชันที่ถูกต้องสำหรับการรับองค์ประกอบสุดท้าย (ของสตรีมที่สั่งซื้อแน่นอน) หรือไม่ คำแถลงของ Brian Goetz ชี้ให้เห็นเพิ่มเติมว่าเอกสาร APIระบุว่าreduce("", String::concat)เป็นวิธีแก้ปัญหาที่ไม่มีประสิทธิภาพ แต่ถูกต้องสำหรับการต่อสายอักขระซึ่งหมายถึงการบำรุงรักษาลำดับการเผชิญหน้าความตั้งใจเป็นที่ทราบกันดีเอกสารประกอบต้องตามทัน
Holger

42

หากคุณมีคอลเล็กชัน (หรือมากกว่าที่สามารถทำซ้ำได้) คุณสามารถใช้ Google Guava

Iterables.getLast(myIterable)

เป็นครีมบำรุงผิวที่มีประโยชน์


1
และคุณสามารถแปลงสตรีมเพื่อทำซ้ำได้อย่างง่ายดาย:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

ซับเดียว (ไม่ต้องสตรีม;):

Object lastElement = list.get(list.size()-1);

30
ArrayIndexOutOfBoundsExceptionถ้ารายการว่างเปล่ารหัสนี้จะโยน
Dragon

8

ฝรั่งมีวิธีเฉพาะสำหรับกรณีนี้:

Stream<T> stream = ...;
Optional<T> lastItem = Streams.findLast(stream);

เทียบเท่ากับstream.reduce((a, b) -> b)แต่ผู้สร้างอ้างว่ามีประสิทธิภาพที่ดีกว่ามาก

จากเอกสารประกอบ :

รันไทม์ของวิธีนี้จะอยู่ระหว่าง O (log n) และ O (n) ซึ่งทำงานได้ดีกว่าบนสตรีมที่แยกได้อย่างมีประสิทธิภาพ

findAny()มันคุ้มค่าที่จะกล่าวถึงว่าถ้ากระแสไม่เรียงลำดับวิธีการนี้จะทำงานเหมือน


1
@ ZhekaKozlov ประเภทของ ... Holger แสดงข้อบกพร่องบางอย่างที่นี่
ยูจีน

0

หากคุณต้องการรับ N จำนวนองค์ประกอบสุดท้าย สามารถใช้การปิดได้ โค้ดด้านล่างนี้จะรักษาคิวภายนอกที่มีขนาดคงที่จนกว่าสตรีมจะสิ้นสุด

    final Queue<Integer> queue = new LinkedList<>();
    final int N=5;
    list.stream().peek((z) -> {
        queue.offer(z);
        if (queue.size() > N)
            queue.poll();
    }).count();

อีกทางเลือกหนึ่งคือใช้การลดการดำเนินการโดยใช้เอกลักษณ์เป็นคิว

    final int lastN=3;
    Queue<Integer> reduce1 = list.stream()
    .reduce( 
        (Queue<Integer>)new LinkedList<Integer>(), 
        (m, n) -> {
            m.offer(n);
            if (m.size() > lastN)
               m.poll();
            return m;
    }, (m, n) -> m);

    System.out.println("reduce1 = " + reduce1);

-1

คุณยังสามารถใช้ฟังก์ชัน skip () ดังต่อไปนี้ ...

long count = data.careas.count();
CArea last = data.careas.stream().skip(count - 1).findFirst().get();

ใช้งานง่ายสุด ๆ


หมายเหตุ: คุณไม่ควรพึ่งพา "การข้าม" ของสตรีมเมื่อจัดการกับคอลเล็กชันขนาดใหญ่ (หลายล้านรายการ) เนื่องจาก "การข้าม" ถูกนำไปใช้โดยการวนซ้ำองค์ประกอบทั้งหมดจนกว่าจะถึงหมายเลข N พยายามแล้ว รู้สึกผิดหวังอย่างมากกับประสิทธิภาพเมื่อเทียบกับการดำเนินการรับโดยดัชนีที่เรียบง่าย
java.is.for.desktop

1
นอกจากนี้หากรายการว่างเปล่าจะโยนArrayIndexOutOfBoundsException
Jindra Vysocký
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.