Отримайте останній елемент потоку / списку в одній вкладиші


118

Як я можу отримати останній елемент потоку чи списку в наступному коді?

Де 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()методу в 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
Guava забезпечує те, Iterables.getLastщо займає Iterable, але оптимізовано для роботи List. Поглядом домашніх тварин є те, що його немає getFirst. StreamAPI взагалі жахливо анальний, опускаючи безліч зручних методів. C # 's LINQ, звичайно, радий надавати .Last()і навіть .Last(Func<T,Boolean> predicate), хоча він також підтримує нескінченні перелічі.
Олександр Дубінський

@AleksandrDubinsky схвалив, але одна примітка для читачів. StreamAPI не є повністю порівнянним LINQз тим, як обидва зроблені в дуже різній парадигмі. Це не гірше і не краще - це просто різне. І, безумовно, деякі методи відсутні не тому, що розробники оракул некомпетентні або середні :)
fasth

1
Для справжнього одношарового покриття ця нитка може бути корисною.
квантовий

Відповіді:


185

Можна отримати останній елемент за допомогою методу Stream :: Reduct . Наступний перелік містить мінімальний приклад для загального випадку:

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

Ця реалізація працює для всіх упорядкованих потоків (включаючи потоки, створені зі списків ). Для невпорядкованих потоків з очевидних причин не визначено, який елемент буде повернутий.

Реалізація працює як для послідовних, так і для паралельних потоків . Це може здивувати на перший погляд, і, на жаль, у документації це не зазначено прямо. Однак це важлива особливість потоків, і я намагаюся уточнити це:

  • Javadoc для методу Stream :: redu заявляє, що його " не обмежено виконувати послідовно " .
  • Javadoc також вимагає, щоб «функція акумулятора повинна являти собою асоціативний , без заважає , без збереження функції для об'єднання двох значень» , яке, очевидно , має місце для лямбда - вираження(first, second) -> second .
  • Javadoc для операцій скорочення зазначає: "Класи потоків мають безліч форм загальних операцій скорочення, званих redu () і collection () [..]", і "правильно побудована операція зменшення по суті є паралельною , доки функція (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підкоряється порядку зустрічей, а операція терміналу вільна ігнорувати порядок зустрічей, навіть якщо потік замовлений. Як осторонь, слово "комутативний" не відображається в javadocs Stream, тому його відсутність не говорить нам багато про що.
Олександр Дубінський

2
@AleksandrDubinsky: Точно в документації не згадується комутативна , оскільки вона не має значення для операції скорочення . Важлива частина: "[..] Правильно побудована операція зменшення за своєю суттю є паралельною, доки функції (функції), які використовуються для обробки елементів, є асоціативними [..]."
ніс

2
@ Олександр Дубінський: звичайно, це не "теоретичне питання специфікації". Це робить різницю між reduce((a,b)->b)правильним рішенням для отримання останнього елемента (звичайно, впорядкованого потоку) чи ні. У заяві Брайана Геца є сенс, подальша документація API стверджує, що reduce("", String::concat)це неефективне, але правильне рішення для конкатенації рядків, що передбачає підтримання порядку зустрічі. Намір добре відомий, документація повинна наздогнати.
Холгер

42

Якщо у вас є колекція (або більш загальнодоступна), ви можете скористатися послугами Google Guava

Iterables.getLast(myIterable)

як зручний oneliner.


1
І ви можете легко перетворити потік в ітерабельний:Iterables.getLast(() -> data.careas.stream().filter(c -> c.bbox.orientationHorizontal).iterator())
shmosel

10

Один вкладиш (немає потреби в потоці;):

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

30
Якщо список порожній, цей код буде кинутий ArrayIndexOutOfBoundsException.
Дракон

8

Гуава виділив метод для цієї справи:

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

Це рівнозначно, stream.reduce((a, b) -> b)але творці стверджують, що він має набагато кращі показники.

З документації :

Час виконання цього методу буде знаходитись між O (log n) та O (n), ефективнішими в ефективних потоках, що розділяються.

Варто згадати, що якщо потік є не упорядкованим, цей метод поводиться так findAny().


1
@ZhekaKozlov роду ... Holger показав деякі недоліки з ним тут
EUGENE

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-го числа. Спробував це. Був дуже розчарований роботою, порівняно з простою операцією get-by-index.
java.is.for.desktop

1
також якщо список порожній, він буде ArrayIndexOutOfBoundsException
кинутий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.