Java8 stream.reduce() с 3 параметъра - получаване на прозрачност

Написах този код, за да намаля списък с думи до дълъг брой на това колко думи започват с „А“. Пиша го просто, за да науча Java 8, така че бих искал да го разбера малко по-добре [Отказ от отговорност: осъзнавам, че това вероятно не е най-добрият начин за писане на този код; това е само за упражнение!].

Long countOfAWords = results.stream().reduce(
    0L,
    (a, b) -> b.charAt(0) == 'A' ? a + 1 : a,
    Long::sum);

Средният параметър/ламбда (наречен акумулатор) изглежда може да намали пълния списък без крайния параметър „Combiner“. Всъщност Javadoc всъщност казва:

Функцията {@code collector} действа като слят картограф и акумулатор, * което понякога може да бъде по-ефективно от отделно картографиране и редукция, * например когато познаването на предишната намалена стойност ви позволява да избегнете * някои изчисления.

[Редактиране от автора] - Следното твърдение е грешно, така че не позволявайте да ви обърква; Просто го пазя тук, за да не разваля оригиналния контекст на отговорите.

Както и да е, мога да заключа, че акумулаторът трябва просто да извежда 1 и 0, които комбинаторът комбинира. Въпреки това не намерих това особено очевидно от документацията.

Моят въпрос

Има ли начин да видя какъв би бил изходът, преди комбинаторът да се изпълни, за да мога да видя списъка с 1 и 0, които комбинаторът комбинира? Това би било полезно при отстраняване на грешки в по-сложни ситуации, на които съм сигурен, че ще попадна в крайна сметка.


person John Humphreys    schedule 03.05.2015    source източник
comment
Защо не просто long countOfAWords=results.stream().filter(x->x.charAt(0)=='A').count();?   -  person Holger    schedule 04.05.2015
comment
Определено можех, но просто си играех с новите механизми за опит; това не беше истински код :)   -  person John Humphreys    schedule 04.05.2015


Отговори (2)


Комбинаторът не намалява списък от 0 и 1. Когато потокът не се изпълнява паралелно, той не се използва в този случай, така че следният цикъл е еквивалентен:

U result = identity;
for (T element : this stream)
    result = accumulator.apply(result, element)
return result;

Когато изпълнявате потока паралелно, задачата се обхваща в множество нишки. Така например данните в тръбопровода са разделени на части, които оценяват и произвеждат резултат независимо. След това комбинаторът се използва за обединяване на тези резултати.

Така че няма да видите редуциран списък, а по-скоро 2 стойности или стойността на идентичността, или с друга стойност, изчислена от задача, които се сумират. Например, ако добавите оператор за печат в комбинатора

(i1, i2) -> {System.out.println("Merging: "+i1+"-"+i2); return i1+i2;}); 

можете да видите нещо подобно:

Merging: 0-0
Merging: 0-0
Merging: 1-0
Merging: 1-0
Merging: 1-1

Това би било полезно при отстраняване на грешки в по-сложни ситуации, на които съм сигурен, че ще попадна в крайна сметка.

По-общо казано, ако искате да видите данните в тръбопровода в движение, можете да използвате peek (или дебъгерът също може да помогне). Така приложено към вашия пример:

long countOfAWords = result.stream().map(s -> s.charAt(0) == 'A' ? 1 : 0).peek(System.out::print).mapToLong(l -> l).sum();

който може да извежда:

100100

[Отказ от отговорност: осъзнавам, че това вероятно не е най-добрият начин да напишете този код; това е само за практика!].

Идиоматичният начин да постигнете задачата си е да filter потока и след това просто да използвате count:

long countOfAWords = result.stream().filter(s -> s.charAt(0) == 'A').count();

Дано помогне! :)

person Alexis C.    schedule 03.05.2015
comment
Това е един от най-добрите SO отговори, които някога съм имал на въпрос :) Благодаря. - person John Humphreys; 03.05.2015
comment
@JohnHumphreys-w00te Уау, благодаря ти много :-) Радвам се, че отговори на въпроса ти! - person Alexis C.; 03.05.2015

Един от начините да видите какво се случва е да замените препратката към метода Long::sum с ламбда, която включва println.

List<String> results = Arrays.asList("A", "B", "A", "A", "C", "A", "A");
Long countOfAWords = results.stream().reduce(
        0L,
        (a, b) -> b.charAt(0) == 'A' ? a + 1 : a,
        (a, b) -> {
            System.out.println(a + " " + b);
            return Long.sum(a, b);
        });

В този случай можем да видим, че комбинаторът всъщност не се използва. Това е така, защото потокът не е успореден. Всичко, което всъщност правим, е да използваме акумулатора, за да комбинираме последователно всяко String с текущия Long резултат; никога не се комбинират две Long стойности.

Ако замените stream с parallelStream, можете да видите, че се използва комбинаторът и да разгледате стойностите, които комбинира.

person Paul Boddington    schedule 03.05.2015
comment
Благодаря и за отговора; това определено беше лесно за използване в моята ситуация. - person John Humphreys; 03.05.2015