Akışın / Listenin son öğesini tek satırda alın


119

Aşağıdaki kodda bir akışın veya listenin son öğesini nasıl alabilirim?

Nerede data.careasbir olduğunu 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?

Gördüğünüz gibi ilk unsuru kesin filterolarak elde etmek zor değil.

Ancak son öğeyi tek satırda almak gerçek bir acıdır:

  • Görünüşe göre onu doğrudan bir Stream. (Yalnızca sonlu akışlar için mantıklı olacaktır)
  • Ayrıca gibi şeyler alamayan görünüyor first()ve last()gelen Listgerçekten acıdır arayüzü.

Arayüzde bir first()ve last()yöntemi sağlamamak için herhangi bir argüman görmüyorum List, çünkü oradaki elemanlar sıralı ve dahası boyut biliniyor.

Ancak orijinal cevaba göre: Sonlu bir öğenin son öğesi nasıl elde edilir Stream?

Şahsen, alabildiğim en yakın şey bu:

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

Bununla birlikte indexOf, her öğede bir kullanmayı içerir ve bu, performansı azaltabileceği için genellikle istemezsiniz.


10
Guava, Iterables.getLastyinelenebilen ancak çalışmak için optimize edilmiş olanı sağlar List. Bir evcil hayvan, sahip olmamasıdır getFirst. StreamGenel API kolaylık yöntemleri çok atlayarak, korkunç anal olduğunu. Constrast gereği C # 'ın LINQ'su, sonsuz Numaralandırmayı da desteklese .Last()bile .Last(Func<T,Boolean> predicate), sağlamaktan mutludur .
Aleksandr Dubinsky

@AleksandrDubinsky oy verdi, ancak okuyucular için bir not. StreamAPI, LINQher ikisi de çok farklı bir paradigmada yapıldığından tam olarak karşılaştırılabilir değildir . Daha kötü ya da daha iyi değil, sadece farklı. Ve kesinlikle bazı yöntemler eksik değil çünkü oracle geliştiriciler yetersiz veya
kaba

1
Gerçek bir tek astar için bu iplik kullanışlı olabilir.
kuantum

Yanıtlar:


186

Son elemanı Stream :: less ile elde etmek mümkündür . Aşağıdaki liste, genel durum için asgari bir örnek içermektedir:

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

Bu uygulamalar tüm sıralı akışlar için çalışır ( Listelerden oluşturulan akışlar dahil ). İçin sırasız akışları o eleman iade edilecektir belirtilmemiş bariz nedenlerden içindir.

Uygulama hem çalışan sıralı ve paralel akışları . Bu ilk bakışta şaşırtıcı olabilir ve maalesef belgeler bunu açıkça belirtmiyor. Ancak akışların önemli bir özelliği ve bunu netleştirmeye çalışıyorum:

  • Yöntemi için Javadoc Akış :: azaltmak o, devletleri "olduğunu değil yürütmek için kısıtlı sırayla " .
  • Javadoc da gerektirir "akümülatörü işlevi bir olmalıdır birleştirici , etkileşmeyen , durum bilgisi iki değerlerinin birleştirilmesi için fonksiyon" tabii ki lambda ifade için de geçerlidir, (first, second) -> second.
  • İçin Javadoc indirgeme işlemleri durumları: "sınıfları genel redüksiyon işlemleri birden fazla formları vardır akışları olarak adlandırılan (azaltma) ve () toplamak [..]" ve "uygun bir şekilde azaltmak inşa işlemdir doğal, paralel , sürece fonksiyonu (s ) elemanlarıdır işlemek için kullanılan birleştirici ve durum bilgisi ."

Yakından ilişkili Toplayıcılar için dokümantasyon daha da belirgindir: " Sıralı ve paralel yürütmelerin eşdeğer sonuçlar üretmesini sağlamak için , toplayıcı işlevlerinin bir kimlik ve bir ilişkilendirme kısıtlamalarını karşılaması gerekir ."


Orijinal soruya geri dönün: Aşağıdaki kod, değişkendeki son öğeye bir başvuru saklar lastve akış boşsa bir istisna atar. Karmaşıklık, akışın uzunluğunda doğrusaldır.

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

Güzel, teşekkürler! Bu arada, _bir parametreye ihtiyaç duymadığınız durumlarda bir adı ihmal etmenin (belki bir veya benzerini kullanarak ) olası olup olmadığını biliyor musunuz ? Öyleyse şöyle olur: .reduce((_, current) -> current)Keşke bu geçerli sözdizimine sahipse.
skiwi

2
@skiwi herhangi bir yasal değişken adı kullanabilirsiniz, örneğin: .reduce(($, current) -> current)veya .reduce((__, current) -> current)(çift alt çizgi).
asylias

2
Teknik olarak herhangi bir akış için çalışmayabilir. Gösterdiğiniz dokümantasyonun yanı sıra, karşılaşma sırasına uyup uymadığını Stream.reduce(BinaryOperator<T>)belirtmez reduceve bir terminal işlemi, akış emri verilse bile karşılaşma sırasını göz ardı etmekte serbesttir. Bir kenara, "değişmeli" kelimesi Stream javadocs'ta görünmüyor, bu yüzden yokluğu bize pek bir şey anlatmıyor.
Aleksandr Dubinsky

2
@AleksandrDubinsky: Dokümantasyonda kesinlikle değişmeli belirtilmiyor çünkü azaltma işlemi ile ilgili değil . Önemli kısım şudur: "[..] Öğeleri işlemek için kullanılan işlev (ler) ilişkisel [..] olduğu sürece, düzgün bir şekilde oluşturulmuş bir azaltma işlemi doğası gereği paralelleştirilebilir."
nosid

2
@Aleksandr Dubinsky: Tabii ki bu "teknik özelliklerin teorik sorusu" değil. reduce((a,b)->b)Son unsurunu (tabii ki sıralı bir akışın) elde etmek için doğru bir çözüm olup olmama arasındaki farkı yaratır . Brian Goetz'in açıklaması bir noktaya değiniyor, ayrıca API dokümantasyonu bunun reduce("", String::concat)dizgi birleştirme için verimsiz ama doğru bir çözüm olduğunu belirtiyor , bu da karşılaşma sırasının sürdürülmesini ima ediyor.
Holger

42

Bir Koleksiyonunuz (veya daha genel bir yinelenebilir) varsa, Google Guava'nın

Iterables.getLast(myIterable)

kullanışlı oneliner olarak.


1
Ve bir akışı kolayca tekrarlanabilir hale dönüştürebilirsiniz:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

Bir astar (akışa gerek yok;):

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

30
Liste boşsa bu kod atılır ArrayIndexOutOfBoundsException.
Dragon

8

Guava'nın bu durum için özel bir yöntemi vardır:

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

Eşdeğeri stream.reduce((a, b) -> b) ancak yaratıcıları çok daha iyi performansa sahiptir iddia ediyor.

Gönderen belgeler :

Bu yöntemin çalışma zamanı O (log n) ile O (n) arasında olacak ve verimli bir şekilde bölünebilir akışlarda daha iyi performans gösterecektir.

Akış sıralı değilse bu yöntemin böyle davrandığını belirtmekte fayda var findAny().


1
@ZhekaKozlov bir çeşit ... Holger burada
Eugene

0

Son N sayıda öğeyi almanız gerekiyorsa. Kapatma kullanılabilir. Aşağıdaki kod, akış sona ulaşıncaya kadar sabit boyutlu harici bir kuyruğu korur.

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

Diğer bir seçenek, kimliği Kuyruk olarak kullanarak azaltma işlemini kullanmak olabilir.

    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

Ayrıca aşağıdaki gibi skip () işlevini de kullanabilirsiniz ...

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

kullanımı çok basit.


Not: Büyük koleksiyonlarla (milyonlarca giriş) uğraşırken akışın "atlamasına" güvenmemelisiniz, çünkü "atlama", N'inci sayıya ulaşılana kadar tüm öğeler boyunca yinelenerek uygulanır. Denedim. Basit bir indekse göre alma işlemine kıyasla performanstan çok hayal kırıklığına uğradı.
java.is.for.desktop

1
ayrıca liste boşsa, ArrayIndexOutOfBoundsException
Jindra Vysocký
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.