Enumerar o mapear a través de una lista con índice y valor en Dart

94

En dardo hay cualquier equivalente al común:

enumerate(List) -> Iterator((index, value) => f)
or 
List.enumerate()  -> Iterator((index, value) => f)
or 
List.map() -> Iterator((index, value) => f)

Parece que esta es la forma más fácil, pero todavía parece extraño que esta funcionalidad no exista.

Iterable<int>.generate(list.length).forEach( (index) => {
  newList.add(list[index], index)
});

Editar:

Gracias a @ hemanth-raj pude encontrar la solución que estaba buscando. Lo pondré aquí para cualquiera que necesite hacer algo similar.

List<Widget> _buildWidgets(List<Object> list) {
    return list
        .asMap()
        .map((index, value) =>
            MapEntry(index, _buildWidget(index, value)))
        .values
        .toList();
}

Alternativamente, puede crear una función de generador síncrono para devolver un iterable

Iterable<MapEntry<int, T>> enumerate<T>(Iterable<T> items) sync* {
  int index = 0;
  for (T item in items) {
    yield MapEntry(index, item);
    index = index + 1;
  }
}

//and use it like this.
var list = enumerate([0,1,3]).map((entry) => Text("index: ${entry.key}, value: ${entry.value}"));
David Rees
fuente
Map#forEach? ¿Esto es lo que quieres?
pskink
Se enumera a través de una lista, no un mapa
David Rees
Map # forEach está enumerando a través de un List? ¿Qué quieres decir? los documentos dicen: "Aplica f a cada par clave / valor del mapa. Llamar a f no debe agregar ni quitar claves del mapa".
pskink
Tampoco entiendo lo que quiere decir con "enumerar o mapear a través de una lista con índice y valor"
Günter Zöchbauer

Respuestas:

143

Existe un asMapmétodo que convierte la lista en un mapa donde las claves son el índice y los valores son el elemento en el índice. Por favor, eche un vistazo a los documentos aquí .

Ejemplo:

List _sample = ['a','b','c'];
_sample.asMap().forEach((index, value) => f);

¡Espero que esto ayude!

Hemanth Raj
fuente
34

No hay una función incorporada para obtener el índice de iteración.

Si, como a mí, no le gusta la idea de construir un Map(la estructura de datos) solo para un índice simple, lo que probablemente quiera es un map(la función) que le da el índice. Llamémoslo mapIndexed(como en Kotlin):

children: mapIndexed(
  list,
  (index, item) => Text("event_$index")
).toList();

La implementación de mapIndexedes simple:

Iterable<E> mapIndexed<E, T>(
    Iterable<T> items, E Function(int index, T item) f) sync* {
  var index = 0;

  for (final item in items) {
    yield f(index, item);
    index = index + 1;
  }
}
Vivien
fuente
1
Esta es una buena respuesta, pero probablemente estaría mejor escrita como un generador síncrono
David Rees
2
@DavidRees ¡gracias por la sugerencia! También cambié el nombre de la funciónmapIndexed
Vivien
Es una pena que Dart no tenga una forma fácil incorporada de hacer esto. ¡Incluso una pitón de 30 años puede hacer esto fácilmente!
osamu
Resulta que .asMap().forEach()es esencialmente lo mismo que esto: vea mi respuesta.
Timmmm
1
@Timmmm Creo asMap()que costará un bucle adicional para recuperar datos, por lo que no será eficiente como el mapIndexed()anterior.
Anticafe
17

Sobre la base de la respuesta de @Hemanth Raj.

Para convertirlo de nuevo, podrías hacer

List<String> _sample = ['a', 'b', 'c'];
_sample.asMap().values.toList(); 
//returns ['a', 'b', 'c'];

O si necesita el índice para una función de mapeo, puede hacer esto:

_sample
.asMap()
.map((index, str) => MapEntry(index, str + index.toString()))
.values
.toList();
// returns ['a0', 'b1', 'c2']
Jørgen Andersen
fuente
14

A partir de Dart 2.7, puede utilizar extensionpara ampliar las funcionalidades en Iterablelugar de tener que escribir métodos auxiliares

extension ExtendedIterable<E> on Iterable<E> {
  /// Like Iterable<T>.map but callback have index as second argument
  Iterable<T> mapIndex<T>(T f(E e, int i)) {
    var i = 0;
    return this.map((e) => f(e, i++));
  }

  void forEachIndex(void f(E e, int i)) {
    var i = 0;
    this.forEach((e) => f(e, i++));
  }
}

Uso:

final inputs = ['a', 'b', 'c', 'd', 'e', 'f'];
final results = inputs
  .mapIndex((e, i) => 'item: $e, index: $i')
  .toList()
  .join('\n');

print(results);

// item: a, index: 0
// item: b, index: 1
// item: c, index: 2
// item: d, index: 3
// item: e, index: 4
// item: f, index: 5
inputs.forEachIndex((e, i) => print('item: $e, index: $i'));

// item: a, index: 0
// item: b, index: 1
// item: c, index: 2
// item: d, index: 3
// item: e, index: 4
// item: f, index: 5
Cerca de Huscarl
fuente
1
esta es la mejor respuesta, esto incluso funciona con elementos de Flutter donde podría devolver Widgets no solo String.
ACERO
7

El paquete más de Lukas Renggli incluye muchas herramientas útiles, incluido "indexado", que hace exactamente lo que quieres. De los documentos:

indexed(['a', 'b'], offset: 1)
  .map((each) => '${each.index}: ${each.value}')
  .join(', ');

(Puede ignorar el argumento de compensación a menos que tenga antecedentes en Smalltalk :-).

Michael Davies
fuente
6

Inicialmente pensé ['one', 'two', 'three'].asMap().forEach((index, value) { ... });que sería realmente ineficiente porque parece que está convirtiendo la lista en un mapa. En realidad no lo es, la documentación dice que crea una vista inmutable de la lista. Verifiqué dos veces con el dart2jsde este código:

void main() {
  final foo = ['one', 'two', 'three'];
  foo.asMap().forEach((idx, val) {
    print('$idx: $val');
  });
}

¡Genera mucho código! Pero la esencia es esta:

  main: function() {
    var foo = H.setRuntimeTypeInfo(["one", "two", "three"], ...);
    new H.ListMapView(foo, ...).forEach$1(0, new F.main_closure());
  },

  H.ListMapView.prototype = {
    forEach$1: function(_, f) {
      var t1, $length, t2, i;
      ...
      t1 = this._values;
      $length = t1.length;
      for (t2 = $length, i = 0; i < $length; ++i) {
        if (i >= t2)
          return H.ioore(t1, i);
        f.call$2(i, t1[i]);
        t2 = t1.length;
        if ($length !== t2)
          throw H.wrapException(P.ConcurrentModificationError$(t1));
      }
    },
    ...
  },

  F.main_closure.prototype = {
    call$2: function(idx, val) {
      ...
      H.printString("" + idx + ": " + H.S(val));
    },
    $signature: 1
  };

¡Así que es lo suficientemente inteligente como para hacer lo eficiente! Muy inteligente.

Por supuesto, también puede usar un bucle for normal:

for (var index = 0; index < values.length; ++index) {
  final value = values[index];
Timmmm
fuente
4

Para mayor comodidad, puede utilizar este método de extensión.

extension CollectionUtil<T> on Iterable<T>  {

  Iterable<E> mapIndexed<E, T>(E Function(int index, T item) transform) sync* {
    var index = 0;

    for (final item in this) {
      yield transform(index, item as T);
      index++;
    }
  }
}
user1998494
fuente
2

Utilice asMap para convertir la lista en mapa primero. El índice de elemento es la clave. El elemento se convierte en valor. Utilice entradas para asignar la clave y el valor a lo que desee.

List rawList = ["a", "b", "c"];
List<String> argList = rawList.asMap().entries.map((e) => '${e.key}:${e.value}').toList();
print(argList);

Salida:

[0:a, 1:b, 2:c]
Gary Lee
fuente