2015-09-30 2 views
46

У меня есть коллекция List<Item>. Мне нужно преобразовать его в Map<Integer, Item> Ключ карты должен быть индексом элемента в коллекции. Я не могу понять, как это сделать с потоками. Что-то вроде:Java 8 список, чтобы карта с потоком

items.stream().collect(Collectors.toMap(...)); 

Любая помощь?

Поскольку этот вопрос определяется как возможный дубликат нужно добавить, что моя конкретная проблема была - как получить позицию элемента в списке и поместить его в качестве значения ключа

+7

[Возможно] (http://stackoverflow.com/questions/20363719/java-8-listv-into-mapk-v) или [возможно] (http://www.leveluplunch.com/java/examples/convert-list-to-map/) или [возможно] (http://www.leveluplunch.com/java/examples/convert-java -util-stream-to-map /) – MadProgrammer

+3

'EntryStream.of (items) .toMap();' используя мою бесплатную библиотеку [StreamEx] (https://github.com/amaembo/streamex). JavaDoc [здесь] (http://amaembo.github.io/streamex/javadoc/javax/util/streamex/EntryStream.html#of-java.util.List-). –

+1

Проделав небольшое исследование для этого вопроса, я узнал, что в java 8 потоках нет ничего «zip». – njzk2

ответ

45

Вы можете создать Stream из индексы, использующие IntStream, а затем преобразовать их в Map:

Map<Integer,Item> map = 
    IntStream.range(0,items.size()) 
      .boxed() 
      .collect(Collectors.toMap (i -> i, i -> items.get(i))); 
+0

, который перестает работать, как только 'items' больше не является' List'. И очень неэффективно, если 'items' является, например,' LinkedList' вместо 'ArrayList'. – njzk2

+0

@ njzk2 См. [Мой ответ] (http://stackoverflow.com/a/32918035/1441122), если у вас нет «списка», или если доступ к индексу очень дорог. –

7

Это обновленный ответ и не имеет ни одной из проблем, упомянутых в комментариях.

Map<Integer,Item> outputMap = IntStream.range(0,inputList.size()).boxed().collect(Collectors.toMap(Function.identity(), i->inputList.get(i))); 
+5

Это не удастся, если в списке повторяется 'Item'. – Misha

+11

Не делайте этого с большими списками. Если вы не хотите учиться на примере, то что означает «O (n²)» ... – Holger

+0

'list.indexOf (i)' is _slow_. Я бы не предложил такой подход. –

8

Не чувствую, что вы должны сделать все в/с потоком. Я бы просто сделать:

AtomicInteger index = new AtomicInteger(); 
items.stream().collect(Collectors.toMap(i -> index.getAndIncrement(), i -> i)); 

До тех пор, пока вы не parallelise поток это будет работать, и это позволяет избежать потенциально дорогостоящим и/или проблематично (в случае дубликатов) get() и indexOf() операции.

(Вы не можете использовать обычную int переменную вместо AtomicInteger потому, что переменные, используемые вне лямбда-выражения должно быть эффективно окончательным. Обратите внимание, что при бесспорной (как в данном случае), AtomicInteger очень быстро и не будет представлять проблема производительности Но если это беспокоит вас вы можете использовать не поточно-счетчик)

+4

Вы вызываете 'List.get()' дорого, но рекомендуем использовать 'AtomicInteger'? – Holger

+4

@Holger No. Я вызываю 'List.get()' * потенциально * дорого. 'AtomicInteger' - O (1),' List.get() 'может быть чем угодно до O (n). –

+3

Только если вы используете 'LinkedList', который не очень полезен. С другой стороны, 'AtomicInteger', являющийся' O (1) ', не имеет особого значения, когда дело касается скрытой стоимости безопасности потоков в операции, которую вы признаете сами, не работает параллельно. Если вы начнете свой ответ с «Не чувствуйте, что вам нужно делать все в/с потоком», почему бы вам не предоставить альтернативу без потока, например, прямолинейную петлю? Это было бы лучше, чем представление непривлекательного использования потока ... – Holger

12

еще одно решение только для полноты использовать пользовательский коллектор:..

public static <T> Collector<T, ?, Map<Integer, T>> toMap() { 
    return Collector.of(HashMap::new, (map, t) -> map.put(map.size(), t), 
      (m1, m2) -> { 
       int s = m1.size(); 
       m2.forEach((k, v) -> m1.put(k+s, v)); 
       return m1; 
      }); 
} 

использование:

Map<Integer, Item> map = items.stream().collect(toMap()); 

Это решение является совместимым с параллельным доступом и не зависит от источника (вы можете использовать список без произвольного доступа или Files.lines() или что-то еще).

+0

это работает, если f/объединитель гарантированно вызывается с m1 и m2 в правильном порядке b/каждая аккумулирующая карта называется на непрерывной последовательности элементов. Это прерывается, если, например, нечетные значения накапливаются на карте и даже значения в другой. Я не нашел источников, которые предполагают, что этого не может произойти. – njzk2

+1

@ njzk2, тем не менее это не могло случиться, если ваш поток упорядочен. Таким образом, все существующие коллекторы (например, 'toList()') действительно работают. –

+0

Думаю, это имеет смысл. Я буду исследовать, как коллекторы гарантируют порядок потока после распараллеливания. – njzk2

1

Используя библиотеку третьей стороны (protonpack, например, но есть и другие) вы можете zip значение с его индексом и вуаля:

StreamUtils.zipWithIndex(items.stream()) 
    .collect(Collectors.toMap(Indexed::getIndex, Indexed::getValue)); 

хотя и getIndex возвращает long, поэтому вам может понадобиться, чтобы бросить его используя что-то похожее на:

i -> Integer.valueOf((int) i.getIndex()) 
+1

Используя библиотеку @TagirValeev, это будет так же просто, как: «EntryStream.of (items) .toMap();'. –

+1

@ Jean-FrançoisSavard, не говоря уже о том, что zipWithIndex создает поток, который не может быть распараллелен вообще. –

+0

@TagirValeev и вы уверены, что 'EntryStream' может? – njzk2

1

Eran's answer обычно лучше подходят для списков произвольного доступа.

Если ваш List не произвольный доступ, или если у вас есть Stream вместо List, вы можете использовать forEachOrdered:

Stream<Item> stream = ... ; 
Map<Integer, Item> map = new HashMap<>(); 
AtomicInteger index = new AtomicInteger(); 
stream.forEachOrdered(item -> map.put(index.getAndIncrement(), item)); 

Это безопасно, если поток параллельно, хотя и назначение карта является небезопасной и работает как побочный эффект. forEachOrdered гарантирует, что элементы обрабатываются по очереди, по порядку. По этой причине маловероятно, что любое ускорение будет результатом параллельной работы. (Возможно, произойдет некоторое ускорение, если есть дорогостоящие операции в трубопроводе до forEachOrdered.)

+1

Почему так сложно? С 'forEachOrdered' вам не нужен' AtomicInteger', просто используйте 'stream.forEachOrdered (item -> map.put (map.size(), item))'. Чтение энергонезависимого поля 'HashMap.size', которое обновляется в любом случае, не хуже, чем использование CAS в' AtomicInteger'. –

+0

@ Тагир Валеев. Я так полагаю. Моим главным моментом является использование 'forEachOrdered', о котором не упоминалось. –

+0

Уверен, что это довольно короткое решение, хотя использование «Коллекционера», предложенное в моей версии, более концептуально корректно. На самом деле лучшим решением было бы использовать 'toList()' и написать специальный адаптер (на основе 'AbstractMap '), который адаптирует «Список » на «Карта ». Хранение их в «HashMap» - это пустая трата времени и памяти. –

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