รวบรวมคู่ต่อเนื่องจากสตรีม


102

ได้รับกระแสเช่น{ 0, 1, 2, 3, 4 },

ฉันจะเปลี่ยนเป็นรูปแบบที่กำหนดอย่างหรูหราที่สุดได้อย่างไร:

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

(สมมติว่าฉันได้กำหนด class Pair ไว้แล้ว)?

แก้ไข:สิ่งนี้ไม่ได้เกี่ยวกับ ints หรือสตรีมดั้งเดิมอย่างเคร่งครัด คำตอบควรเป็นคำตอบทั่วไปสำหรับสตรีมทุกประเภท


2
คำที่มาจาก FP คือ "พาร์ติชัน" แต่ฉันไม่พบเมธอดที่มีความหมายที่ต้องการใน Java มีการแบ่งพาร์ติชันบนเพรดิเคต
Marko Topolnik

1
โดยทั่วไปแล้ว Spliterator ใน JDK 8 ถูกคิดเพื่อวัตถุประสงค์ในการข้ามและแบ่ง ฉันจะพยายามสร้างตัวอย่างด้วย
Olimpiu POP

list.stream().map(i -> new Pair(i, i+1));
aepurniet

2
สำหรับคำถามที่ไม่เทียบเท่าสตรีมโปรดดูที่stackoverflow.com/questions/17453022/…
Raedwald

อย่างไรก็ตามบางคนใช้การใช้งานMap.Entryเป็นคลาส Pair (จริงอยู่บางคนอาจคิดว่าเป็นการแฮ็ก แต่การใช้คลาสในตัวนั้นสะดวก)
Basil Bourque

คำตอบ:


33

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

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();

สำหรับสตรีมวัตถุคุณสามารถสร้างวัตถุประเภทอื่นได้ ไลบรารีของฉันไม่มีโครงสร้างข้อมูลใหม่ที่ผู้ใช้มองเห็นได้เช่นPair(ซึ่งเป็นส่วนหนึ่งของแนวคิดไลบรารี) อย่างไรก็ตามหากคุณมีPairคลาสของตัวเองและต้องการใช้งานคุณสามารถดำเนินการดังต่อไปนี้:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);

หรือหากคุณมีอยู่แล้วStream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);

ฟังก์ชั่นนี้จะดำเนินการโดยใช้spliterator ที่กำหนดเอง มีค่าใช้จ่ายค่อนข้างต่ำและสามารถขนานกันได้อย่างสวยงาม แน่นอนว่ามันใช้งานได้กับแหล่งสตรีมใด ๆ ไม่ใช่แค่รายการ / อาร์เรย์การเข้าถึงแบบสุ่มเหมือนโซลูชันอื่น ๆ ในการทดสอบหลายครั้งทำได้ดีมาก นี่คือเกณฑ์มาตรฐาน JMH ที่เราพบค่าอินพุตทั้งหมดที่นำหน้าค่าที่มากกว่าโดยใช้วิธีการต่างๆ (ดูคำถามนี้ )


ขอบคุณ! ยิ่งฉันศึกษาห้องสมุดนี้มากเท่าไหร่ฉันก็ยิ่งรักมันมากขึ้นเท่านั้น ในที่สุดฉันก็อาจเริ่มใช้สตรีม ( StreamEximplements Iterable! Hurray!)
Aleksandr Dubinsky

เพื่อให้คำตอบของคุณสมบูรณ์ 100% คุณสามารถแสดงวิธีการStreamรวมเป็นStreamEx?
Aleksandr Dubinsky

3
@AleksandrDubinsky: แค่ใช้StreamEx.of(stream). มีวิธีการคงที่ที่สะดวกอื่น ๆ ในการสร้างสตรีมจากCollectionอาร์เรย์Readerฯลฯ แก้ไขคำตอบ
Tagir Valeev

@TagirValeev ได้pairMapรับคำสั่งในสตรีมต่อเนื่อง? อันที่จริงฉันต้องการใช้ forPairsOr ผง () แต่เนื่องจากไม่มีวิธีการดังกล่าวฉันสามารถจำลองได้หรือไม่? stream.ordered().forPairs()หรือstream().pairMap().forEachOrdered()?
Askar Kalykov

1
@AskarKalykov ที่pairMapเป็นงานกลางที่ไม่ใช่การแทรกแซงการทำงานไร้สัญชาติ mapper mapการสั่งซื้อที่ไม่ได้ระบุไว้สำหรับมันในทางเช่นเดียวกับการที่เรียบง่าย forPairsคือเรียงลำดับโดยสเปค แต่การดำเนินการเรียงลำดับเป็นพฤตินัยสั่งให้ลำธารตามลำดับ คงจะดีถ้าคุณกำหนดปัญหาเดิมของคุณเป็นคำถาม stackoverflow แยกต่างหากเพื่อให้มีบริบทมากขึ้น
Tagir Valeev

74

ไลบรารีสตรีม Java 8 ส่วนใหญ่มุ่งเน้นไปที่การแยกสตรีมออกเป็นชิ้นเล็ก ๆ สำหรับการประมวลผลแบบขนานดังนั้นขั้นตอนไปป์ไลน์ที่มีสถานะค่อนข้าง จำกัด และไม่รองรับการทำสิ่งต่างๆเช่นการรับดัชนีขององค์ประกอบสตรีมปัจจุบันและการเข้าถึงองค์ประกอบสตรีมที่อยู่ติดกัน

วิธีทั่วไปในการแก้ปัญหาเหล่านี้โดยมีข้อ จำกัด บางประการคือการขับเคลื่อนสตรีมด้วยดัชนีและอาศัยการประมวลผลค่าในโครงสร้างข้อมูลการเข้าถึงโดยสุ่มเช่น ArrayList ซึ่งสามารถดึงข้อมูลองค์ประกอบได้ หากค่าอยู่ในarrayListหนึ่งสามารถสร้างคู่ตามที่ร้องขอโดยทำสิ่งนี้:

    IntStream.range(1, arrayList.size())
             .mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
             .forEach(System.out::println);

แน่นอนข้อ จำกัด คืออินพุตไม่สามารถเป็นสตรีมที่ไม่มีที่สิ้นสุดได้ ไปป์ไลน์นี้สามารถทำงานแบบขนานได้


5
"อินพุตต้องไม่เป็นสตรีมที่ไม่มีที่สิ้นสุด" จริงๆแล้วอินพุตไม่สามารถเป็นสตรีมได้เลย อินพุต ( arrayList) เป็นคอลเลคชันซึ่งเป็นสาเหตุที่ฉันไม่ทำเครื่องหมายว่าเป็นคำตอบ (แต่ขอแสดงความยินดีกับป้ายทองของคุณ!)
Aleksandr Dubinsky

16

สิ่งนี้ไม่สวยหรูเป็นวิธีแก้ปัญหาที่แฮ็ก แต่ใช้ได้กับสตรีมที่ไม่มีที่สิ้นสุด

Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
    new Function<Integer, Pair>() {
        Integer previous;

        @Override
        public Pair apply(Integer integer) {
            Pair pair = null;
            if (previous != null) pair = new Pair(previous, integer);
            previous = integer;
            return pair;
        }
    }).skip(1); // drop first null

ตอนนี้คุณสามารถ จำกัด สตรีมได้ตามความยาวที่คุณต้องการ

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));

ปล.ฉันหวังว่าจะมีทางออกที่ดีกว่านี้เช่น clojure(partition 2 1 stream)


6
ความรุ่งโรจน์ในการชี้ให้เห็นว่าคลาสที่ไม่ระบุชื่อเป็นทางเลือกที่มีประโยชน์ในบางครั้งสำหรับ lambdas
Aleksandr Dubinsky

2
@aepurniet ฉันคิดว่ามันทำงานไม่ถูกต้อง ตามparallelStreamเอกสาร: "เพื่อรักษาพฤติกรรมที่ถูกต้องพารามิเตอร์พฤติกรรมเหล่านี้ต้องไม่รบกวนและในกรณีส่วนใหญ่จะต้องเป็นคนไร้สัญชาติ"
mishadoff

14
สิ่งนี้ตรงกันข้ามอย่างสิ้นเชิงกับการออกแบบเฟรมเวิร์กสตรีมและละเมิดสัญญาของแผนที่ API โดยตรงเนื่องจากฟังก์ชันที่ไม่ระบุตัวตนไม่ใช่คนไร้สัญชาติ ลองเรียกใช้สิ่งนี้กับสตรีมคู่ขนานและข้อมูลเพิ่มเติมเพื่อให้เฟรมเวิร์กสตรีมสร้างเธรดที่ใช้งานได้มากขึ้นและคุณจะเห็นผลลัพธ์: "ข้อผิดพลาด" แบบสุ่มซึ่งไม่บ่อยนักแทบจะเป็นไปไม่ได้ที่จะเกิดซ้ำและตรวจจับได้ยากจนกว่าคุณจะมีข้อมูลเพียงพอ (ในการผลิต?) สิ่งนี้อาจเป็นหายนะ
Mario Rossi

4
@AleksandrDubinsky คุณไม่ถูกต้องเกี่ยวกับขีด จำกัด / การข้ามเป็นแบบขนาน การใช้งานที่ให้ไว้ใน JDK นั้นทำงานควบคู่กันไป เนื่องจากการดำเนินการเชื่อมโยงกับลำดับการเผชิญหน้าการขนานอาจไม่ได้ให้ประโยชน์ด้านประสิทธิภาพเสมอไป แต่ในสถานการณ์ที่มี Q สูงก็สามารถทำได้
Brian Goetz

4
@AleksandrDubinsky ไม่ถูกต้อง. มันอาจข้ามองค์ประกอบแบบสุ่มหากสตรีมไม่มีการเรียงลำดับ (ไม่มีลำดับการเผชิญหน้าที่กำหนดไว้ดังนั้นในทางเหตุผลจึงไม่มีองค์ประกอบ "แรก" หรือ "ที่ n" เป็นเพียงองค์ประกอบ) แต่ไม่ว่าสตรีมจะเรียงลำดับหรือไม่เรียงลำดับการข้ามก็ทำได้เสมอ เพื่อทำงานควบคู่กัน มีความขนานน้อยกว่าที่จะแยกหากมีการสั่งสตรีม แต่ยังคงขนานกัน
Brian Goetz

15

ฉันได้ติดตั้งตัวแยกกระดาษห่อตัวซึ่งนำทุกnองค์ประกอบTจากตัวแยกต้นฉบับและสร้างList<T>:

public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {

    private final Spliterator<T> wrappedSpliterator;

    private final int n;

    private final Deque<T> deque;

    private final Consumer<T> dequeConsumer;

    public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
        this.wrappedSpliterator = wrappedSpliterator;
        this.n = n;
        this.deque = new ArrayDeque<>();
        this.dequeConsumer = deque::addLast;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<T>> action) {
        deque.pollFirst();
        fillDeque();
        if (deque.size() == n) {
            List<T> list = new ArrayList<>(deque);
            action.accept(list);
            return true;
        } else {
            return false;
        }
    }

    private void fillDeque() {
        while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
            ;
    }

    @Override
    public Spliterator<List<T>> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return wrappedSpliterator.estimateSize();
    }

    @Override
    public int characteristics() {
        return wrappedSpliterator.characteristics();
    }
}

อาจใช้วิธีต่อไปนี้เพื่อสร้างสตรีมติดต่อกัน:

public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
    Spliterator<E> spliterator = stream.spliterator();
    Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
    return StreamSupport.stream(wrapper, false);
}

ตัวอย่างการใช้งาน:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);

ทำซ้ำทุกองค์ประกอบสองครั้งหรือไม่?
Aleksandr Dubinsky

ไม่ สร้างสตรีมใหม่ที่มีList<E>องค์ประกอบ แต่ละรายการมีnองค์ประกอบที่ต่อเนื่องกันจากสตรีมต้นฉบับ ตรวจสอบเอง;)
Tomek Rękawek

คุณสามารถแก้ไขคำตอบเพื่อให้ทุกองค์ประกอบ (ยกเว้นข้อแรกและข้อสุดท้าย) ซ้ำได้หรือไม่?
Aleksandr Dubinsky

4
+1 ฉันคิดว่านี่เป็นงานที่ดีและควรกำหนดให้มีขนาดขั้นตอนใด ๆ นอกเหนือจากขนาดพาร์ติชัน มีความจำเป็นอย่างมากสำหรับ(partition size step)ฟังก์ชันและนี่คือวิธีที่ดีที่สุดในการใช้งาน
Marko Topolnik

3
พิจารณาใช้สำหรับการปฏิบัติงานในการตั้งค่าArrayDeque LinkedList
Marko Topolnik

14

คุณสามารถทำได้ด้วยเมธอด Stream.reduce () (ฉันไม่เห็นคำตอบอื่นใดที่ใช้เทคนิคนี้)

public static <T> List<Pair<T, T>> consecutive(List<T> list) {
    List<Pair<T, T>> pairs = new LinkedList<>();
    list.stream().reduce((a, b) -> {
        pairs.add(new Pair<>(a, b));
        return b;
    });
    return pairs;
}

1
มันจะส่งคืน (1,2) (2,3) แทนที่จะเป็น (1,2) (3,4) นอกจากนี้ฉันไม่แน่ใจว่าจะใช้ตามลำดับหรือไม่ (แน่นอนว่าไม่มีการรับประกันเช่นนั้น)
Aleksandr Dubinsky

1
โปรดตรวจสอบคำถามนั่นคือพฤติกรรมที่ตั้งใจไว้ @Aleksandr Dubinsky
SamTebbs33

4
อ่าใช่ขอโทษ และเมื่อคิดว่าฉันเขียนมัน
Aleksandr Dubinsky


6

คุณสามารถทำได้ในcyclops-react (ฉันมีส่วนร่วมในไลบรารีนี้) โดยใช้ตัวดำเนินการเลื่อน

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

หรือ

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

สมมติว่าตัวสร้างคู่สามารถยอมรับคอลเล็กชันที่มี 2 องค์ประกอบได้

หากคุณต้องการจัดกลุ่มด้วย 4 และเพิ่มทีละ 2 ที่รองรับด้วย

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

วิธีการคงที่เท่ากันสำหรับการสร้างมุมมองแบบเลื่อนบน java.util.stream.Stream ยังจัดเตรียมไว้ในคลาสStreamUtils ของ cyclops -stream

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

หมายเหตุ: - สำหรับการดำเนินการแบบเธรดเดียว ReactiveSeq จะเหมาะสมกว่า LazyFutureStream ขยาย ReactiveSeq แต่ส่วนใหญ่มุ่งเน้นสำหรับการใช้งานพร้อมกัน / คู่ขนาน (เป็น Stream of Futures)

LazyFutureStream ขยาย ReactiveSeq ซึ่งขยาย Seq จากjOOλที่ยอดเยี่ยม (ซึ่งขยาย java.util.stream.Stream) ดังนั้นการนำเสนอโซลูชันของ Lukas ก็จะทำงานร่วมกับประเภทสตรีม สำหรับใครก็ตามที่สนใจความแตกต่างหลักระหว่างตัวดำเนินการหน้าต่าง / บานเลื่อนคือการแลกเปลี่ยนพลังงาน / ความซับซ้อนสัมพัทธ์ที่เห็นได้ชัดและความเหมาะสมสำหรับการใช้งานกับสตรีมที่ไม่มีที่สิ้นสุด (การเลื่อนไม่กินกระแส แต่จะบัฟเฟอร์เมื่อมันไหล)


ด้วยวิธีนี้คุณจะได้รับ [(0,1) (2,3) ... ] แต่คำถามถามว่า [(0,1) (1,2) ... ] โปรดดูคำตอบของฉันกับ RxJava ...
59

1
คุณพูดถูกฉันไม่ดีฉันอ่านคำถามผิด - ตัวดำเนินการเลื่อนเป็นตัวดำเนินการที่ถูกต้องที่จะใช้ที่นี่ ฉันจะอัปเดตคำตอบของฉัน - ขอบคุณ!
John McClean

4

ห้องสมุดโปรตอนแพ็คมีฟังก์ชั่นหน้าต่าง ด้วยคลาส Pair และ Stream คุณสามารถทำได้ดังนี้:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);

ตอนนี้pairsสตรีมประกอบด้วย:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on

อย่างไรก็ตามดูเหมือนว่าคุณต้องสร้างstสองครั้ง! ไลบรารีนี้สามารถแก้ปัญหาโดยใช้สตรีมเดียวได้หรือไม่
Aleksandr Dubinsky

@AleksandrDubinsky ฉันไม่คิดว่าจะมีให้กับตัวแยกสัญญาณปัจจุบัน ฉันส่งปัญหาgithub.com/poetix/protonpack/issues/9
Alexis C.

@AleksandrDubinsky เพิ่มwindowedฟังก์ชันแล้ว! ดูการแก้ไข
Alexis C.

1
ทำไมคุณไม่ลบคำตอบเดิมของคุณเพื่อให้ผู้ใช้รายอื่นสามารถดูวิธีแก้ปัญหาไม่ใช่ประวัติ
Aleksandr Dubinsky

4

การหาคู่ที่ต่อเนื่องกัน

หากคุณยินดีที่จะใช้ไลบรารีของบุคคลที่สามและไม่จำเป็นต้องใช้การขนานกันjOOλมีฟังก์ชันหน้าต่างรูปแบบ SQL ดังนี้

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window()
   .filter(w -> w.lead().isPresent())
   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
   .toList()
);

การให้ผลผลิต

[(0, 1), (1, 2), (2, 3), (3, 4)]

lead()ฟังก์ชั่นการเข้าถึงค่าถัดไปเพื่อสำรวจเส้นทางจากหน้าต่าง

การค้นหาสามเท่าต่อเนื่อง / สี่เท่า / n-tuples

คำถามในความคิดเห็นกำลังขอวิธีแก้ปัญหาที่กว้างขึ้นโดยที่ไม่ควรจับคู่ แต่ควรรวบรวม n-tuples (หรืออาจเป็นรายการ) นี่จึงเป็นแนวทางอื่น:

int n = 3;

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window(0, n - 1)
   .filter(w -> w.count() == n)
   .map(w -> w.window().toList())
   .toList()
);

การให้รายชื่อรายการ

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

หากไม่มีfilter(w -> w.count() == n)ผลลัพธ์ก็จะเป็น

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]

ข้อจำกัดความรับผิดชอบ: ฉันทำงานให้กับ บริษัท ที่อยู่เบื้องหลังjOOλ


น่าสนใจ. จะเกิดอะไรขึ้นถ้าฉันต้องการจัดกลุ่ม 3 องค์ประกอบขึ้นไป ใช้w.lead().lead()?
Raul Santelices

1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))จะเป็นตัวเลือก ฉันได้อัปเดตคำตอบของฉันด้วยวิธีแก้ปัญหาทั่วไปสำหรับlength = n
Lukas Eder

1
ฉันเข้าใจถูกต้องว่า.window()ไม่ใช่การดำเนินการที่ขี้เกียจซึ่งรวบรวมสตรีมอินพุตทั้งหมดไว้ในคอลเลกชันกลางบางส่วนจากนั้นสร้างสตรีมใหม่จากนั้น?
Tagir Valeev

@TagirValeev: ใช่นั่นคือการใช้งานปัจจุบัน ในกรณีข้างต้น (ไม่มีการComparatorใช้เพื่อเรียงลำดับหน้าต่างใหม่) การเพิ่มประสิทธิภาพเช่นนี้จะเป็นไปได้และมีแนวโน้มที่จะนำไปใช้ในอนาคต
Lukas Eder


2

เราสามารถใช้RxJava ( ไลบรารีส่วนขยายปฏิกิริยาที่มีประสิทธิภาพมาก)

IntStream intStream  = IntStream.iterate(1, n -> n + 1);

Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);

pairObservable.take(10).forEach(b -> {
            b.forEach(n -> System.out.println(n));
            System.out.println();
        });

ตัวดำเนินการบัฟเฟอร์จะ แปลง Observable ที่ปล่อยไอเท็มเป็น Observable ที่ปล่อยคอลเลคชันบัฟเฟอร์ของไอเท็มเหล่านั้น ..


1
ฉันใช้Observable.zip(obs, obs.skip(1), pair->{...})มาจนถึงตอนนี้! ฉันไม่รู้ว่าObservable.bufferมีเวอร์ชันที่มีขั้นตอน (และฉันคุ้นเคยกับzipเคล็ดลับจาก python) +1
Reut Sharabani

1

การดำเนินการนั้นมีสถานะเป็นหลักดังนั้นจึงไม่ใช่สิ่งที่สตรีมมีไว้เพื่อแก้ปัญหา - ดูส่วน "พฤติกรรมไร้สัญชาติ" ในjavadoc :

แนวทางที่ดีที่สุดคือหลีกเลี่ยงพารามิเตอร์พฤติกรรมที่ระบุสถานะเพื่อสตรีมการดำเนินการทั้งหมด

ทางออกหนึ่งที่นี่คือการแนะนำสถานะในสตรีมของคุณผ่านตัวนับภายนอกแม้ว่าจะใช้ได้กับสตรีมแบบต่อเนื่องเท่านั้น

public static void main(String[] args) {
    Stream<String> strings = Stream.of("a", "b", "c", "c");
    AtomicReference<String> previous = new AtomicReference<>();
    List<Pair> collect = strings.map(n -> {
                            String p = previous.getAndSet(n);
                            return p == null ? null : new Pair(p, n);
                        })
                        .filter(p -> p != null)
                        .collect(toList());
    System.out.println(collect);
}


static class Pair<T> {
    private T left, right;
    Pair(T left, T right) { this.left = left; this.right = right; }
    @Override public String toString() { return "{" + left + "," + right + '}'; }
}

คำถามขอให้รวบรวมองค์ประกอบที่ต่อเนื่องกันของสตรีมอินพุตไม่ใช่เพียงเพื่อรวบรวมจำนวนเต็มต่อเนื่อง คำอธิบายที่สำคัญของคำศัพท์: Stream! = "lambdas"
Aleksandr Dubinsky

คุณสามารถแทนที่ AtomicInteger ด้วย AtomicReference อีกทางเลือกหนึ่งคือม้วนตัวรวบรวมของคุณเองหรือใช้ไลบรารีภายนอกเช่นในตัวอย่างนี้stackoverflow.com/a/30090528/829571
assylias

ดูการแก้ไขของฉัน นอกจากนี้ฉันไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณเกี่ยวกับ lambda! = stream คำตอบอื่น ๆ ที่ใช้คลาสที่ไม่ระบุตัวตนก็ทำสิ่งเดียวกันเป็นหลักยกเว้นว่าสถานะนั้นถูกยึดโดยคลาสนิรนามแทนที่จะเป็นภายนอก ...
assylias

1
ที่ได้ผล StreamExห้องสมุดยังเป็นที่หาที่ดีและอาจเป็นคำตอบในตัวเอง ความคิดเห็นของฉันเกี่ยวกับ "stream! = lambdas" หมายถึงคุณที่ระบุว่า "การดำเนินการนั้นมีสถานะเป็นหลักดังนั้นจึงไม่ใช่สิ่งที่ lambdas มีไว้เพื่อแก้ปัญหา" ฉันคิดว่าคุณตั้งใจจะใช้คำว่า "สตรีม"
Aleksandr Dubinsky

โอ้ฉันเข้าใจ - ฉันชี้แจงแล้ว
assylias

0

ในกรณีของคุณฉันจะเขียน IntFunction ที่กำหนดเองซึ่งจะติดตาม int ล่าสุดที่ผ่านและใช้เพื่อแมป IntStream ดั้งเดิม

import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class PairFunction implements IntFunction<PairFunction.Pair> {

  public static class Pair {

    private final int first;
    private final int second;

    public Pair(int first, int second) {
      this.first = first;
      this.second = second;
    }

    @Override
    public String toString() {
      return "[" + first + "|" + second + "]";
    }
  }

  private int last;
  private boolean first = true;

  @Override
  public Pair apply(int value) {
    Pair pair = !first ? new Pair(last, value) : null;
    last = value;
    first = false;
    return pair;
  }

  public static void main(String[] args) {

    IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
    final PairFunction pairFunction = new PairFunction();
    intStream.mapToObj(pairFunction)
        .filter(p -> p != null) // filter out the null
        .forEach(System.out::println); // display each Pair

  }

}

ปัญหานี้คือการปล่อยให้คนไร้สัญชาติออกไปนอกหน้าต่าง
Rob

@ ร็อบแล้วมีปัญหาอะไร
Aleksandr Dubinsky

ประเด็นหลักประการหนึ่งของแลมบ์ดาคือการไม่มีสถานะที่ไม่แน่นอนเพื่อให้ตัวรวมภายในสามารถทำงานแบบขนานกันได้
Rob

@ ร็อบ: ใช่คุณพูดถูก แต่สตรีมตัวอย่างที่กำหนดนั้นท้าทายความเท่าเทียมกันอยู่ดีเนื่องจากแต่ละรายการ (ยกเว้นรายการแรกและรายการสุดท้าย) จะใช้เป็นรายการแรกและรายการที่สองของบางคู่
jpvee

@jpvee ใช่ฉันคิดว่านั่นคือสิ่งที่คุณคิด ฉันสงสัยว่าถ้าไม่มีวิธีทำกับนักทำแผนที่คนอื่น ๆ โดยพื้นฐานแล้วสิ่งที่คุณต้องการจะเทียบเท่ากับการทำให้ตัวเพิ่มการวนซ้ำไปทีละสองครั้งจากนั้นให้ functor รับ 2 อาร์กิวเมนต์ สิ่งนั้นจะต้องเป็นไปได้
Rob

0

สำหรับการคำนวณความแตกต่างต่อเนื่องในเวลา (ค่า x) ของอนุกรมเวลาผมใช้stream's collect(...)วิธีการ:

final List< Long > intervals = timeSeries.data().stream()
                    .map( TimeSeries.Datum::x )
                    .collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
                    .intervals();

โดยที่ DifferenceCollector เป็นดังนี้:

public class DifferenceCollector implements LongConsumer
{
    private final List< Long > intervals = new ArrayList<>();
    private Long lastTime;

    @Override
    public void accept( final long time )
    {
        if( Objects.isNull( lastTime ) )
        {
            lastTime = time;
        }
        else
        {
            intervals.add( time - lastTime );
            lastTime = time;
        }
    }

    public void combine( final DifferenceCollector other )
    {
        intervals.addAll( other.intervals );
        lastTime = other.lastTime;
    }

    public List< Long > intervals()
    {
        return intervals;
    }
}

คุณอาจปรับเปลี่ยนสิ่งนี้ให้เหมาะกับความต้องการของคุณได้


0

ในที่สุดฉันก็หาวิธีหลอก Stream.reduce เพื่อให้สามารถจัดการกับคู่ค่าได้อย่างเรียบร้อย มีกรณีการใช้งานมากมายที่ต้องใช้สิ่งอำนวยความสะดวกนี้ซึ่งไม่ปรากฏตามธรรมชาติใน JDK 8:

public static int ArithGeo(int[] arr) {
    //Geometric
    List<Integer> diffList = new ArrayList<>();
    List<Integer> divList = new ArrayList<>();
    Arrays.stream(arr).reduce((left, right) -> {
        diffList.add(right-left);
        divList.add(right/left);
        return right;
    });
    //Arithmetic
    if(diffList.stream().distinct().count() == 1) {
        return 1;
    }
    //Geometric
    if(divList.stream().distinct().count() == 1) {
        return 2;
    }
    return -1;
}

เคล็ดลับที่ฉันใช้คือการคืนสิทธิ์ คำให้การ.


1
ฉันไม่คิดว่าreduceจะให้หลักประกันเพียงพอสำหรับการทำงานนี้
Aleksandr Dubinsky

จะสนใจที่จะทราบข้อมูลเพิ่มเติมเกี่ยวกับการค้ำประกันเพียงพอ ช่วยอธิบายให้ละเอียดหน่อยได้ไหม? อาจมีทางเลือกในฝรั่ง ... แต่ฉันมีข้อ จำกัด และไม่สามารถใช้มันได้
Beezer

-1

ทางออกที่สวยงามจะใช้ซิป สิ่งที่ต้องการ:

List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
                                      input.stream().substream(1),
                                      (a, b) -> new Pair(a, b)
);

สิ่งนี้ค่อนข้างรัดกุมและสวยงาม แต่จะใช้รายการเป็นอินพุต ไม่สามารถประมวลผลแหล่งสตรีมที่ไม่มีที่สิ้นสุดด้วยวิธีนี้

อีกปัญหาหนึ่ง (ที่ยุ่งยากกว่ามาก) คือ zip พร้อมกับคลาส Streams ทั้งหมดได้ถูกลบออกจาก API เมื่อเร็ว ๆ นี้ โค้ดด้านบนใช้ได้กับb95หรือรุ่นเก่ากว่าเท่านั้น ดังนั้นด้วย JDK ล่าสุดฉันจะบอกว่าไม่มีโซลูชันสไตล์ FP ที่หรูหราและตอนนี้เราสามารถหวังได้ว่าในทางใดทางหนึ่ง zip จะได้รับการแนะนำให้รู้จักกับ API


แท้จริงzipถูกลบออก ฉันจำสิ่งที่อยู่ในStreamsชั้นเรียนไม่ได้ทั้งหมดแต่มีบางอย่างที่ย้ายไปเป็นวิธีการแบบคงที่บนStreamอินเทอร์เฟซและยังมีStreamSupportและStream.Builderคลาสด้วย
Stuart Marks

ถูกตัอง. วิธีการอื่น ๆ เช่น concat หรือการวนซ้ำถูกย้ายและกลายเป็นวิธีการเริ่มต้นในสตรีม น่าเศร้าที่เพิ่งลบ zip ออกจาก API ฉันเข้าใจเหตุผลเบื้องหลังตัวเลือกนี้ (เช่นไม่มีทูเปิลส์) แต่ก็ยังเป็นฟีเจอร์ที่ดี
แกดเจ็ต

2
@gadget ทูเปิลเกี่ยวอะไรกับzip? zipไม่ว่าเหตุผลอวดความรู้อาจจะมีการคิดค้นไม่ปรับฆ่า
Aleksandr Dubinsky

@AleksandrDubinsky ในกรณีส่วนใหญ่ zip จะใช้เพื่อสร้างคอลเลกชันของ Pairs / Tuples เป็นเอาต์พุต พวกเขาแย้งว่าถ้าพวกเขาเก็บซิปไว้คนก็จะขอ Tuples เป็นส่วนหนึ่งของ JDK เช่นกัน ฉันจะไม่ลบฟีเจอร์ที่มีอยู่ออกไปเลย
แกดเจ็ต

-1

นี่คือปัญหาที่น่าสนใจ. ความพยายามไฮบริดของฉันต่ำกว่าดีหรือไม่?

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3);
    Iterator<Integer> first = list.iterator();
    first.next();
    if (first.hasNext())
        list.stream()
        .skip(1)
        .map(v -> new Pair(first.next(), v))
        .forEach(System.out::println);
}

ฉันเชื่อว่ามันไม่ได้ยืมตัวไปใช้กับการประมวลผลแบบขนานและด้วยเหตุนี้จึงอาจถูกตัดสิทธิ์


คำถามไม่ได้ขอการประมวลผลแบบขนาน แต่ถือว่าเรามีเพียง a Streamไม่ใช่ a List. แน่นอนเราสามารถงัดตัววนซ้ำจากสตรีมได้เช่นกันดังนั้นนี่อาจเป็นวิธีแก้ปัญหาที่ถูกต้อง อย่างไรก็ตามมันเป็นแนวทางดั้งเดิม
Aleksandr Dubinsky

-1

ดังที่คนอื่น ๆ สังเกตเห็นว่ามีอยู่เนื่องจากลักษณะของปัญหาจึงจำเป็นต้องมีสถานะบางอย่าง

ฉันประสบกับปัญหาที่คล้ายกันซึ่งฉันต้องการสิ่งที่เป็นหลักของฟังก์ชัน Oracle SQL LEAD ความพยายามของฉันในการใช้งานที่อยู่ด้านล่าง

/**
 * Stream that pairs each element in the stream with the next subsequent element.
 * The final pair will have only the first item, the second will be null.
 */
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
    final Iterator<T> input = stream.sequential().iterator();

    final Iterable<Pair<T>> iterable = () ->
    {
        return new Iterator<Pair<T>>()
        {
            Optional<T> current = getOptionalNext(input);

            @Override
            public boolean hasNext()
            {
                return current.isPresent();
            }

            @Override
            public Pair<T> next()
            {
                Optional<T> next = getOptionalNext(input);
                final Pair<T> pair = next.isPresent()
                    ? new Pair(current.get(), next.get())
                    : new Pair(current.get(), null);
                current = next;

                return pair;
            }
        };
    };

    return iterable.spliterator();
}

private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.empty();
}

-1

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

ตัวอย่างแรก Belows กำหนดอินสแตนซ์ของคลาส BoundedQueue ซึ่งจะจัดเก็บองค์ประกอบที่ส่งผ่านสตรีม (หากคุณไม่ชอบความคิดในการขยาย LinkedList โปรดดูลิงก์ที่กล่าวถึงข้างต้นสำหรับแนวทางอื่นและวิธีการทั่วไป) หลังจากนั้นคุณก็จะรวมสององค์ประกอบที่ตามมาเป็นคู่:

public class TwoSubsequentElems {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));

    class BoundedQueue<T> extends LinkedList<T> {
      public BoundedQueue<T> save(T curElem) {
        if (size() == 2) { // we need to know only two subsequent elements
          pollLast(); // remove last to keep only requested number of elements
        }

        offerFirst(curElem);

        return this;
      }

      public T getPrevious() {
        return (size() < 2) ? null : getLast();
      }

      public T getCurrent() {
        return (size() == 0) ? null : getFirst();
      }
    }

    BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();

    final List<Pair<Integer>> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
      .collect(Collectors.toList());

    answer.forEach(System.out::println);
  }
}

-3

ฉันเห็นด้วยกับ @aepurniet แต่คุณต้องใช้ mapToObj แทน

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);

1
ขวา. แต่นี่เป็นการรวบรวมคู่ของจำนวนเต็มไม่ใช่คู่ขององค์ประกอบของสตรีม (ประเภทใดก็ได้)
Aleksandr Dubinsky

-5

รันforลูปที่ตั้งแต่ 0 ถึงlength-1สตรีมของคุณ

for(int i = 0 ; i < stream.length-1 ; i++)
{
    Pair pair = new Pair(stream[i], stream[i+1]);
    // then add your pair to an array
}

3
และแลมด้าเป็นส่วนหนึ่งของการแก้ปัญหาอยู่ที่ไหน?
Olimpiu POP

ไม่ใช่กรณีที่สตรีมไม่มีที่สิ้นสุด
mishadoff

@Olimpiu - คุณได้แลมด้ามาจากไหน? ฉันอ่านคำถามสองครั้งเพื่อให้แน่ใจว่าฉันไม่พลาด ฉันยังตรวจสอบประวัติการแก้ไข และคำถามไม่ได้ถูกแท็กด้วย
jww
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.