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);