java java-8 java-stream

java - ¿Cómo asignar elementos a su índice usando flujos?



java-8 java-stream (6)

Obtengo una secuencia de algunos objetos personalizados y me gustaría crear un mapa Map<Integer, MyObject> con el índice de cada objeto como clave. Para darle un ejemplo simple:

Stream<String> myStream = Arrays.asList("one","two","three").stream(); Integer i = 0; Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> i++, x -> x));

Obviamente, esto no se compila porque:

Las variables locales a las que se hace referencia desde una expresión lambda deben ser finales o efectivamente finales

¿Hay una manera simple de asignar elementos de una secuencia a sus índices para que la salida esperada para el ejemplo anterior sea algo así como:

{1=one, 2=two, 3=three}


Podemos usar el List.indexOf(Object o) para obtener el índice del elemento en la lista, mientras construimos el Map :

List<String> list = Arrays.asList("one","two","three"); Map<Integer, String> result = list.stream() .collect(Collectors.toMap( k -> list.indexOf(k) + 1, Function.identity(), (v1, v2) -> v2)); System.out.println(result);

Si hay duplicados en la lista, el índice de la primera aparición del elemento se agregará en el mapa final. También para resolver el error de fusión en el mapa durante una colisión de teclas, debemos asegurarnos de que haya una función de fusión incluida en toMap(keyMapper, valueMapper, mergeFunction)

Salida:

{1=one, 2=two, 3=three}


Prueba esto :

Digamos String[] array = { "V","I","N","A","Y" }; entonces,

Arrays.stream(array) .map(ele-> index.getAndIncrement() + " -> " + ele) .forEach(System.out::println);

Salida:

0 -> V 1 -> I 2 -> N 3 -> A 4 -> Y


Puede usar un IntStream para resolver esto:

List<String> list = Arrays.asList("one","two","three"); Map<Integer, String> map = IntStream.range(0, list.size()).boxed() .collect(Collectors.toMap(Function.identity(), list::get));

Crea un IntStream de 0 a list.size() - 1 ( IntStream.range() excluye el último valor de la secuencia) y IntStream.range() cada índice al valor de su lista. La ventaja de esta solución es que también funcionará con flujos paralelos, lo que no es posible con el uso de un AtomicInteger .

Entonces el resultado en este caso sería:

{0=one, 1=two, 2=three}

Para comenzar el primer índice en 1 , solo puede agregar 1 durante la recopilación:

List<String> list = Arrays.asList("one", "two", "three"); Map<Integer, String> map = IntStream.range(0, list.size()).boxed() .collect(Collectors.toMap(i -> i + 1, list::get));

Esto resultará en esto:

{1=one, 2=two, 3=three}


Tu variable no es efectivamente final.

Puede usar AtomicInteger como contenedor Integer :

Stream<String> myStream = Arrays.asList("one","two","three").stream(); AtomicInteger atomicInteger = new AtomicInteger(0); Map<Integer, String> result3 = myStream.collect(Collectors.toMap(x -> atomicInteger.getAndIncrement(), Function.identity()));

Lo considero un poco hacky porque solo resuelve el problema de la variable final efectiva. Dado que es una versión especial de ThreadSafe, podría introducir algunos gastos generales. La solución de stream puro en la respuesta de Samuel Philipp podría adaptarse mejor a sus necesidades.


Una solución limpia que no requiere datos de origen de acceso aleatorio, es

Map<Integer,String> result = Stream.of("one", "two", "three") .collect(HashMap::new, (m,s) -> m.put(m.size() + 1, s), (m1,m2) -> { int offset = m1.size(); m2.forEach((i,s) -> m1.put(i + offset, s)); });

Esto también funciona con flujos paralelos.

En el caso poco probable de que esta sea una tarea recurrente, vale la pena poner la lógica en un recopilador reutilizable, incluidas algunas optimizaciones:

public static <T> Collector<T,?,Map<Integer,T>> toIndexMap() { return Collector.of( HashMap::new, (m,s) -> m.put(m.size() + 1, s), (m1,m2) -> { if(m1.isEmpty()) return m2; if(!m2.isEmpty()) { int offset = m1.size(); m2.forEach((i,s) -> m1.put(i + offset, s)); } return m1; }); }

Que luego se puede usar como

Map<Integer,String> result = Stream.of("one", "two", "three") .collect(MyCollectors.toIndexMap());

o

Map<Integer,Integer> result = IntStream.rangeClosed(1, 1000) .boxed().parallel() .collect(MyCollectors.toIndexMap());


Guava tiene un método estático Streams#mapWithIndex

Stream<String> myStream = Stream.of("one","two","three"); Map<Long, String> result3 = Streams.mapWithIndex(myStream, (s, i) -> Maps.immutableEntry(i + 1, s)) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // {1=one, 2=two, 3=three} System.out.println(result3);