2015-04-29 3 views
6

У меня есть поток слов, и я хотел бы отсортировать их в соответствии с появлением одинаковых элементов (= слов).Java-потоки | groupingBy те же элементы

например: {Привет, мир, привет}

в

Map<String, List<String>> 

привет, {привет, привет}

мир, {мир}

Что я до сих пор :

Map<Object, List<String>> list = streamofWords.collect(Collectors.groupingBy(???)); 

P roblem 1: Поток, кажется, теряет информацию о том, что он обрабатывает строки, поэтому компилятор заставляет меня изменить тип на Object, List

Проблема 2: Я не знаю, что помещать внутри родителя, чтобы сгруппировать его в том же случае. Я знаю, что я могу обрабатывать отдельные элементы внутри лямбда-выражения, но я понятия не имею, как достичь «снаружи» каждого элемента, чтобы проверить равенство.

Спасибо

ответ

5

KeyExtractor вы ищете является функция тождества:

Map<String, List<String>> list = streamofWords.collect(Collectors.groupingBy(Function.identity())); 

EDIT добавлено объяснение:

  • Function.identity() Retuns 'функции' с одним методом, который ничего не делает больше, чем возврат аргумента, который он получает.
  • Collectors.groupingBy(Function<S, K> keyExtractor) обеспечивает сборщик, который собирает все элементы потока до Map<K, List<S>>. Он использует реализацию keyExtractor для проверки объектов потока типа S и выводит из них ключ K. Этот ключ является ключом карты, используемым для получения (или создания) списка в карте результатов, к которой добавлен элемент потока.
+0

Спасибо, я дам вам принятый ответ, как только подтвержу это. P.S .: Не могли бы вы объяснить, как это работает? Особенно, какая стратегия, когда я хотел бы получать метаданные элементов в списке, когда я использую потоки? Как-то я, похоже, не понимаю. Я думаю, что это может быть полезно для будущих читателей. – SklogW

+2

@SklogW Добавлено объяснение. Теперь понятно? – flo

+0

Да, немного, еще одна мелочь: «s -> s» как выражение лямбда действительно достаточно, чтобы подразумевать, что мне нужны элементы с одинаковой идентичностью. Я не понимаю, как сказать функции, что мне нужны одни и те же элементы. s -> s.equals (s) был бы вздор, не так ли? – SklogW

7

Чтобы получить Map<String, List<String>>, вам просто нужно сказать в groupingBy коллектор, который вы хотите сгруппировать значения от идентичности, поэтому функция x -> x.

Map<String, List<String>> occurrences = 
    streamOfWords.collect(groupingBy(str -> str)); 

Однако это немного бесполезно, поскольку вы видите, что у вас есть тот же тип информации два раза. Вы должны посмотреть в Map<String, Long>, где значение указывает на появление строки в потоке.

Map<String, Long> occurrences = 
    streamOfWords.collect(groupingBy(str -> str, counting())); 

В принципе, вместо того, чтобы иметь groupingBy, которые возвращают значения, как List, вы используете вниз по течению коллектора counting(), чтобы сказать, что вы хотите, чтобы подсчитать, сколько раз появляется это значение.

Ваше требование рода должно означать, что вы должны иметь Map<Long, List<String>> (что, если различные строки появляются одинаковое число раз?), А в качестве коллектора по умолчанию toMap возвращает HashMap, он не имеет понятия упорядоченности, но вы можете хранить элементы вместо TreeMap.


Я попытался кратко изложить то, что я сказал в комментариях.

У вас возникли проблемы с тем, как str -> str может определить, отличаются ли «привет» или «мир».

Прежде всего str -> str - это функция, то есть для входа x значение f (x). Например, f(x) = x + 2 - это функция, которая для любого значения x возвращает x + 2.

Здесь мы используем функцию тождества, то есть f(x) = x. Когда вы собираете элементы из конвейера в Map, эта функция будет вызываться раньше, чтобы получить ключ от значения. Итак, в вашем примере у вас есть 3 элемента, для которых функция идентификации дает:

f("hello") = "hello" 
f("world") = "world" 

Пока все хорошо.

Теперь, когда вызывается collect(), для каждого значения в потоке вы будете применять к нему функцию и оценивать результат (который будет ключом в Map). Если ключ уже существует, мы берем текущее сопоставленное значение и объединяем в List значение, которое мы хотели поставить (то есть значение, из которого вы только что применили функцию), с этим предыдущим сопоставленным значением. Вот почему вы получаете Map<String, List<String>> в конце.

Возьмем еще один пример. Теперь поток содержит значения «hello», «world» и «hey», а функция, которую мы хотим применить для группировки элементов, - это str -> str.substring(0, 2), то есть функция, которая принимает первые два символа String.

Кроме того, мы имеем:

f("hello") = "he" 
f("world") = "wo" 
f("hey") = "he" 

Здесь вы видите, что и «привет» и «эй» дает один и тот же ключ, при применении функции и, следовательно, они будут сгруппированы в одной и той же List при сборе, так что конечный результат:

"he" -> ["hello", "hey"] 
"wo" -> ["world"] 

Чтобы иметь аналогию с математикой, вы могли бы принять любые не-биективные функции, такие как х . Для x = -2 и x = 2 у нас есть, что f(x) = 4. Поэтому, если мы сгруппировали целые числа по этой функции, -2 и 2 были бы в одной и той же «сумке».

Глядя на исходный код, вы не сможете сразу понять, что происходит. Это полезно, если вы хотите знать, как это реализовано под капотом. Но сначала попробуйте подумать о концепции с более высоким уровнем абстракции, а затем, возможно, все станет понятнее.

Надеюсь, это поможет! :)

+0

Не могли бы вы вкратце объяснить, как можно собирать метаданные элементов в списке при использовании потоков? Я понимаю, что каждый элемент обрабатывается один за другим, но как он работает под капотом? Уровень абстракции при использовании потоков немного сбивает меня с толку. – SklogW

+2

@SklogW С какими конкретными деталями у вас проблемы? Исходный код не очень прост для понимания. Для изображения высокого уровня 'groupingBy' является сборщиком, который накапливает элемент в' Map 'при прохождении конвейера на ходу. Помните, что 'str -> str' - это функция, поэтому вместо выполнения' map.put (str, someValue) 'вы будете делать' map.put (function.apply (str), someValue) '. Если у вас есть два идентичных ключа, вы объединяете значения в списке и т. Д. Информация о типе сохраняется, так как 'Function' является универсальным интерфейсом, поэтому вы знаете, какой тип вы получите после его применения. –

+0

В нашем случае 'function.apply (str)' будет возвращать 'str', потому что это функция идентификации, поэтому в принципе каждая строка, которая является одинаковой (в соответствии с равенствами), будет объединена в один список, содержащий все эти строки для создания значение, связанное с ключом. Вот почему у вас есть 'Map >' в конце. –

Смежные вопросы