Énumérer ou mapper via une liste avec index et valeur dans Dart

94

En fléchette il y a tout équivalent au commun:

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

Il semble que ce soit le moyen le plus simple, mais il semble toujours étrange que cette fonctionnalité n'existerait pas.

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

Éditer:

Grâce à @ hemanth-raj, j'ai pu trouver la solution que je recherchais. Je vais mettre ceci ici pour tous ceux qui ont besoin de faire quelque chose de similaire.

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

Vous pouvez également créer une fonction de générateur synchrone pour renvoyer un itérable

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
la source
Map#forEach? c'est ce que tu veux?
pskink
Il énumère à travers une liste pas une carte
David Rees
La carte # forEach est énumérée via un List? Que voulez-vous dire? les documents disent: "Applique f à chaque paire clé / valeur de la carte. L'appel de f ne doit pas ajouter ou supprimer des clés de la carte."
pskink
Je ne comprends pas non plus ce que vous entendez par "énumérer ou mapper via une liste avec index et valeur"
Günter Zöchbauer

Réponses:

143

Il existe une asMapméthode qui convertit la liste en une carte où les clés sont l'index et les valeurs sont l'élément à l'index. Veuillez consulter la documentation ici .

Exemple:

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

J'espère que cela t'aides!

Hemanth Raj
la source
34

Il n'y a pas de fonction intégrée pour obtenir l'index d'itération.

Si comme moi vous n'aimez pas l'idée de construire une Map(la structure de données) juste pour un simple index, ce que vous voulez probablement est une map(la fonction) qui vous donne l'index. Appelons-le mapIndexed(comme dans Kotlin):

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

La mise en œuvre de mapIndexedest 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
la source
1
C'est une bonne réponse mais serait probablement mieux écrite comme un générateur synchrone
David Rees
2
@DavidRees merci pour la suggestion! J'ai aussi renommé la fonctionmapIndexed
Vivien
C'est dommage que dart n'ait pas de moyen facile intégré de le faire. Même un python âgé de 30 ans peut le faire facilement!
osamu
Il s'avère que .asMap().forEach()c'est essentiellement la même chose - voir ma réponse.
Timmmm
1
@Timmmm Je pense que asMap()cela coûtera une boucle supplémentaire pour récupérer les données, donc cela ne sera pas efficace comme mapIndexed()ci-dessus.
anticafe le
17

S'appuyant sur la réponse @Hemanth Raj.

Pour le reconvertir, vous pouvez le faire

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

Ou si vous avez besoin de l'index pour une fonction de mappage, vous pouvez le faire:

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

À partir de Dart 2.7, vous pouvez utiliser extensionpour étendre les fonctionnalités de Iterableau lieu d'avoir à écrire des méthodes d'assistance

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

Usage:

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
Près de Huscarl
la source
1
c'est la meilleure réponse, cela fonctionne même avec les éléments Flutter où vous pouvez renvoyer des widgets pas seulement des chaînes.
STEEL
7

Le package plus de Lukas Renggli comprend de nombreux outils utiles, y compris «indexé» qui fait exactement ce que vous voulez. À partir de la documentation:

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

(Vous pouvez ignorer l'argument offset sauf si vous avez un arrière-plan Smalltalk :-).

Michael Davies
la source
6

J'ai d'abord pensé que ce ['one', 'two', 'three'].asMap().forEach((index, value) { ... });serait vraiment inefficace car il semble que cela convertisse la liste en carte. En fait, ce n'est pas le cas - la documentation dit que cela crée une vue immuable de la liste. J'ai vérifié avec le dart2jsde ce code:

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

Cela génère beaucoup de code! Mais l'essentiel est ceci:

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

Il est donc assez intelligent pour faire la chose efficace! Assez intelligent.

Bien sûr, vous pouvez également utiliser une boucle for normale:

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

Pour plus de commodité, vous pouvez utiliser cette méthode d'extension.

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
la source
2

Utilisez asMap pour convertir la liste en carte en premier. L'index d'élément est la clé. L'élément devient valeur. Utilisez des entrées pour mapper la clé et la valeur à tout ce que vous voulez.

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

Production:

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