Enumere ou mapeie por meio de uma lista com índice e valor no Dart

99

No dardo há qualquer equivalente ao comum:

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

Parece que esta é a maneira mais fácil, mas ainda parece estranho que essa funcionalidade não existisse.

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

Editar:

Graças a @ hemanth-raj, consegui encontrar a solução que procurava. Vou colocar isso aqui para quem precisa fazer algo semelhante.

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

Alternativamente, você pode criar uma função de gerador síncrono para retornar um iterável

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
fonte
Map#forEach? é o que você quer?
pskink
É enumerado por meio de uma lista, não de um mapa
David Rees
Map # forEach está enumerando por meio de um List? O que você quer dizer? os documentos dizem: "Aplica f a cada par de chave / valor do mapa. Chamar f não deve adicionar ou remover chaves do mapa."
pskink
Eu também não entendo o que você quer dizer com "enumerar ou mapear por meio de uma lista com índice e valor"
Günter Zöchbauer

Respostas:

152

Existe um asMapmétodo que converte a lista em um mapa onde as chaves são o índice e os valores são o elemento no índice. Por favor, dê uma olhada na documentação aqui .

Exemplo:

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

Espero que isto ajude!

Hemanth Raj
fonte
35

Não há função integrada para obter o índice de iteração.

Se, como eu, você não gosta da ideia de construir a Map(a estrutura de dados) apenas para um índice simples, o que você provavelmente quer é a map(a função) que fornece o índice. Vamos chamá-lo mapIndexed(como em Kotlin):

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

A implementação de mapIndexedé simples:

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
fonte
1
Esta é uma boa resposta, mas provavelmente seria melhor escrita como um gerador síncrono
David Rees
2
@DavidRees obrigado pela sugestão! Eu também renomei a funçãomapIndexed
Vivien
1
É uma pena que o dardo não tenha uma maneira fácil embutida de fazer isso. Até python de 30 anos pode fazer isso facilmente!
osamu
Acontece que .asMap().forEach()é essencialmente o mesmo - veja minha resposta.
Timmmm
1
@Timmmm acho que asMap()vai custar loop adicional para recuperar dados, portanto, não será eficiente como mapIndexed()acima.
anticafe
18

Com base na resposta de @Hemanth Raj.

Para convertê-lo de volta, você poderia fazer

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

Ou se você precisasse do índice para uma função de mapeamento, você poderia fazer isso:

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

A partir do Dart 2.7, você pode usar extensionpara estender as funcionalidades ao Iterableinvés de ter que escrever 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
NearHuscarl
fonte
2
esta é a melhor resposta, isso funciona até mesmo com itens Flutter onde você pode retornar Widgets, não apenas String.
STEEL
8

O pacote de mais de Lukas Renggli inclui muitas ferramentas úteis, incluindo 'indexado' que faz exatamente o que você deseja. Dos documentos:

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

(Você pode ignorar o argumento de deslocamento, a menos que tenha um plano de fundo Smalltalk :-).

Michael Davies
fonte
7

Inicialmente, pensei ['one', 'two', 'three'].asMap().forEach((index, value) { ... });que seria realmente ineficiente porque parece que ele está convertendo a lista em um mapa. Na verdade, não é - a documentação diz que ele cria uma visão imutável da lista. Verifiquei duas vezes com o dart2jsdeste código:

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

Ele gera muito código! Mas a essência é 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
  };

Portanto, é inteligente o suficiente para fazer a coisa eficiente! Muito inteligente.

Claro, você também pode usar apenas um loop for normal:

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

Por conveniência, você pode usar este método de extensão.

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
fonte
3

Use asMap para converter a lista em mapa primeiro. O índice do elemento é a chave. O elemento se torna valor. Use entradas para mapear a chave e o valor para qualquer coisa que você quiser.

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

Resultado:

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

Você pode usar a Iterable.generatefábrica. O código a seguir mapearia um Iterableusando índices e valores.

extension IterableMapIndex<T> on Iterable<T> {
  Iterable<E> mapIndexed<E>(E f(int index, T t)) {
    return Iterable.generate(this.length, (index)=>f(index, elementAt(index)));
  }
}
navid
fonte