Menghitung atau memetakan melalui daftar dengan indeks dan nilai di Dart

95

Di dart ada yang setara dengan yang umum:

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

Tampaknya ini adalah cara termudah tetapi masih terlihat aneh bahwa fungsi ini tidak akan ada.

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

Edit:

Terima kasih kepada @ hemanth-raj saya dapat menemukan solusi yang saya cari. Saya akan meletakkan ini di sini untuk siapa saja yang perlu melakukan hal serupa.

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

Alternatively you could create a synchronous generator function to return a 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
sumber
Map#forEach? is it what you want?
pskink
It's enumerating through a List not a Map
David Rees
Map#forEach is enumerating through a List? what do you mean? the docs say: "Applies f to each key/value pair of the map. Calling f must not add or remove keys from the map."
pskink
I also do not understand what you mean with "enumerate or map through a list with index and value"
Günter Zöchbauer

Jawaban:

145

There is a asMap method which converts the list to a map where the keys are the index and values are the element at index. Please take a look at the docs here.

Example:

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

Hope this helps!

Hemanth Raj
sumber
34

There is no built-in function to get the iteration index.

If like me you don't like the idea to build a Map (the data structure) just for a simple index, what you probably want is a map (the function) which gives you the index. Let's call it mapIndexed (like in Kotlin):

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

The implementation of mapIndexed is 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
sumber
1
This is a good answer but would probably be better written as a synchronous generator
David Rees
2
@DavidRees thx for the suggestion! I also renamed the function mapIndexed
Vivien
It's a shame that dart does not have a built-in easy way to do this. Even 30 years old python can do this easily!
osamu
It turns out that .asMap().forEach() is essentially the same as this - see my answer.
Timmmm
1
@Timmmm I think asMap() will cost additional loop to retrieve data, thus it will not efficient as mapIndexed() above.
anticafe
17

Building on @Hemanth Raj answer.

To convert it back you could do

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

Or if you needed the index for a mapping function you could do this:

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

Starting with Dart 2.7, you can use extension to extend the functionalities of Iterable instead of having to write helper methods

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
NearHuscarl
sumber
1
this is the best answer, this even works with Flutter items where you could return Widgets not just String.
STEEL
7

Lukas Renggli's more package includes many useful tools including 'indexed' which does exactly what you want. From the docs:

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

(You can ignore the offset argument unless you have a Smalltalk background :-).

Michael Davies
sumber
6

I initially thought ['one', 'two', 'three'].asMap().forEach((index, value) { ... }); would be really inefficient because it looks like it is converting the list to a map. Actually it isn't - the documentation says it creates an immutable view of the list. I double checked with the dart2js of this code:

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

It generates lot of code! But the gist is this:

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

So it is smart enough to do the efficient thing! Pretty clever.

Of course you can also just use a normal for loop:

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

For convenience you can use this extension method.

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
sumber
2

Use asMap to convert List to map first. The index of element is the key. The element becomes value. Use entries to map the key and value to anything you want.

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

Output:

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