Wylicz lub zmapuj listę z indeksem i wartością w Dart

94

W dart jest jakikolwiek odpowiednik wspólnego:

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

Wydaje się, że to najłatwiejszy sposób, ale nadal wydaje się dziwne, że taka funkcjonalność nie istniała.

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

Edytować:

Dzięki @ hemanth-raj udało mi się znaleźć rozwiązanie, którego szukałem. Umieszczę to tutaj dla każdego, kto musi zrobić coś podobnego.

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

Alternatywnie możesz utworzyć synchroniczną funkcję generatora, aby zwrócić iterowalną

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
źródło
Map#forEach? czy tego chcesz
pskink
To wyliczanie poprzez List, a nie mapę
David Rees,
Mapa # forEach wylicza za pomocą List? co masz na myśli? Dokumentacja mówi: „Stosuje f do każdej pary klucz / wartość mapy. Wywołanie f nie może dodawać ani usuwać kluczy z mapy”.
pskink
Ja też nie rozumiem co masz na myśli z „enumerate lub map przez liście z indeksem i wartości”
Günter Zochbauer

Odpowiedzi:

143

Istnieje asMapmetoda, która konwertuje listę na mapę, w której klucze są indeksem, a wartości są elementem w indeksie. Proszę spojrzeć na dokumenty tutaj .

Przykład:

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

Mam nadzieję że to pomoże!

Hemanth Raj
źródło
34

Nie ma wbudowanej funkcji do uzyskania indeksu iteracji.

Jeśli tak jak ja nie podoba ci się pomysł zbudowania Map(struktury danych) tylko dla prostego indeksu, prawdopodobnie potrzebujesz map(funkcji), która daje ci indeks. Nazwijmy to mapIndexed(jak w Kotlinie):

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

Wdrożenie mapIndexedjest proste:

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
źródło
1
To dobra odpowiedź, ale prawdopodobnie lepiej by ją napisać jako generator synchroniczny
David Rees
2
@DavidRees thx za sugestię! Zmieniłem również nazwę funkcjimapIndexed
Vivien
Szkoda, że ​​dart nie ma wbudowanego łatwego sposobu na zrobienie tego. Nawet 30-letni Python może to łatwo zrobić!
osamu
Okazuje się, że w .asMap().forEach()zasadzie jest to to samo - zobacz moją odpowiedź.
Timmmm
1
@Timmmm Myślę, że asMap()będzie kosztować dodatkową pętlę do pobierania danych, więc nie będzie wydajna jak mapIndexed()powyżej.
anticafe
17

Opierając się na odpowiedzi @Hemanth Raj.

Aby go przekonwertować, możesz zrobić

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

Lub jeśli potrzebujesz indeksu do funkcji mapowania, możesz to zrobić:

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

Począwszy od Dart 2.7, możesz extensionrozszerzyć funkcjonalność Iterablezamiast pisać metody pomocnicze

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

Stosowanie:

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
źródło
1
to najlepsza odpowiedź, działa to nawet z elementami Flutter, w których można zwrócić Widgety, a nie tylko ciąg.
STAL
7

Więcej w pakiecie Lukasa Renggliego zawiera wiele przydatnych narzędzi, w tym „indeksowane”, które robi dokładnie to, co chcesz. Z dokumentów:

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

(Możesz zignorować argument przesunięcia, chyba że masz tło Smalltalk :-).

Michael Davies
źródło
6

Początkowo myślałem ['one', 'two', 'three'].asMap().forEach((index, value) { ... });, że byłoby to naprawdę nieefektywne, ponieważ wygląda na to, że konwertuje listę na mapę. W rzeczywistości tak nie jest - dokumentacja mówi, że tworzy niezmienny widok listy. Dokładnie sprawdziłem dart2jsten kod:

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

Generuje dużo kodu! Ale istota jest taka:

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

Jest więc wystarczająco inteligentny, aby robić rzeczy wydajne! Całkiem sprytnie.

Oczywiście możesz też użyć zwykłej pętli for:

for (var index = 0; index < values.length; ++index) {
  final value = values[index];
Timmmm
źródło
4

Dla wygody możesz skorzystać z tej metody rozszerzenia.

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
źródło
2

Użyj asMap, aby najpierw przekonwertować List do mapy. Indeks elementu jest kluczem. Element staje się wartością. Użyj wpisów, aby zmapować klucz i wartość na cokolwiek chcesz.

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

Wynik:

[0:a, 1:b, 2:c]
Gary Lee
źródło