Menggunakan map () pada iterator

92

Katakanlah kita memiliki Peta :, let m = new Map();menggunakan m.values()mengembalikan iterator peta.

Tetapi saya tidak dapat menggunakan forEach()atau map()pada iterator itu dan mengimplementasikan loop sementara pada iterator itu tampak seperti anti-pola karena ES6 menawarkan fungsi seperti map().

Jadi, apakah ada cara untuk menggunakan map()iterator?

shinzou
sumber
Tidak di luar kotak, tetapi Anda dapat menggunakan pustaka pihak ketiga seperti lodash mapfungsi yang mendukung Peta juga.
berbahaya
Map itu sendiri memiliki forEach untuk melakukan iterasi terhadap pasangan nilai kuncinya.
berbahaya
Mengonversi iterator ke array dan memetakannya seperti Array.from(m.values()).map(...)berhasil, tetapi saya pikir itu bukan cara terbaik untuk melakukan ini.
JiminP
masalah mana yang ingin Anda selesaikan dengan menggunakan iterator sementara array akan lebih cocok untuk digunakan Array#map?
Nina Scholz
1
@NinaScholz Saya menggunakan set umum seperti di sini: stackoverflow.com/a/29783624/4279201
shinzou

Jawaban:

84

Cara termudah dan paling tidak berkinerja untuk melakukan ini adalah:

Array.from(m).map(([key,value]) => /* whatever */)

Lebih baik

Array.from(m, ([key, value]) => /* whatever */))

Array.frommengambil hal yang dapat diulang atau seperti larik dan mengubahnya menjadi larik! Seperti yang Daniel tunjukkan di komentar, kita dapat menambahkan fungsi pemetaan ke konversi untuk menghapus iterasi dan selanjutnya array perantara.

Menggunakan Array.fromakan bergerak kinerja Anda dari O(1)ke O(n)sebagai titik @hraban keluar di komentar. Karena madalah a Map, dan mereka tidak bisa tidak terbatas, kita tidak perlu khawatir tentang urutan yang tidak terbatas. Untuk kebanyakan kasus, ini sudah cukup.

Ada beberapa cara lain untuk memutar melalui peta.

Menggunakan forEach

m.forEach((value,key) => /* stuff */ )

Menggunakan for..of

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
  console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one
ktilcu.dll
sumber
Bisakah peta memiliki panjang tak terbatas?
ktilcu
2
@ktilcu untuk iterator: ya. .map pada iterator dapat dianggap sebagai transformasi pada generator, yang mengembalikan iterator itu sendiri. popping satu elemen memanggil iterator yang mendasari, mengubah elemen, dan mengembalikannya.
hraban
8
Masalah dengan jawaban ini adalah mengubah algoritma memori O (1) menjadi O (n), yang cukup serius untuk dataset yang lebih besar. Selain, tentu saja, membutuhkan iterator yang terbatas dan tidak dapat dialirkan. Judul pertanyaannya adalah "Menggunakan map () pada iterator", saya tidak setuju bahwa urutan malas dan tak terbatas bukan bagian dari pertanyaan. Itulah tepatnya cara orang menggunakan iterator. "Peta" hanyalah sebuah contoh ("Katakan .."). Hal yang baik tentang jawaban ini adalah kesederhanaannya, yang sangat penting.
hraban
1
@hraban Terima kasih telah menambahkan diskusi ini. Saya dapat memperbarui jawaban untuk memasukkan beberapa peringatan agar calon wisatawan memiliki info depan dan pusat. Ketika sampai pada itu kita sering harus membuat keputusan antara kinerja yang sederhana dan optimal. Saya biasanya akan berpihak pada yang lebih sederhana (untuk men-debug, memelihara, menjelaskan) kinerja.
ktilcu
3
@ktilcu Anda dapat memanggil Array.from(m, ([key,value]) => /* whatever */)(perhatikan fungsi pemetaan ada di dalam from) dan kemudian tidak ada larik perantara yang dibuat ( sumber ). Ini masih berpindah dari O (1) ke O (n), tetapi setidaknya iterasi dan pemetaan terjadi hanya dalam satu iterasi penuh.
Daniel
19

Anda dapat menentukan fungsi iterator lain untuk mengulang ini:

function* generator() {
    for (let i = 0; i < 10; i++) {
        console.log(i);
        yield i;
    }
}

function* mapIterator(iterator, mapping) {
    for (let i of iterator) {
        yield mapping(i);
    }
}

let values = generator();
let mapped = mapIterator(values, (i) => {
    let result = i*2;
    console.log(`x2 = ${result}`);
    return result;
});

console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));

Sekarang Anda mungkin bertanya: mengapa tidak menggunakan Array.fromsaja? Karena ini akan berjalan melalui seluruh iterator, simpan ke array (sementara), iterasikan lagi dan kemudian lakukan pemetaan. Jika daftarnya sangat besar (atau bahkan berpotensi tidak terbatas) ini akan menyebabkan penggunaan memori yang tidak perlu.

Tentu saja, jika daftar item cukup kecil, penggunaan Array.fromharus lebih dari cukup.

sheean
sumber
Bagaimana jumlah memori yang terbatas dapat menahan struktur data yang tidak terbatas?
shinzou
3
tidak, itulah intinya. Dengan ini, Anda dapat membuat "aliran data" dengan merangkai sumber iterator ke sekumpulan transformasi iterator dan terakhir sink konsumen. Misalnya untuk streaming pemrosesan audio, bekerja dengan file besar, agregator pada database, dll.
hraban
1
Saya suka jawaban ini. Adakah yang bisa merekomendasikan pustaka yang menawarkan metode mirip Array pada iterable?
Joel Malone
1
mapIterator()tidak menjamin bahwa iterator yang mendasari akan ditutup dengan benar ( iterator.return()dipanggil) kecuali nilai kembalian berikutnya dipanggil setidaknya sekali. Lihat: repeater.js.org/docs/safety
Jaka Jančar
Mengapa Anda menggunakan protokol iterator secara manual, bukan hanya for .. of .. loop?
cowlicks
11

Cara paling sederhana dan paling berhasil ini adalah dengan menggunakan argumen kedua Array.fromuntuk mencapai ini:

const map = new Map()
map.set('a', 1)
map.set('b', 2)

Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']

Pendekatan ini bekerja untuk iterable non-infinite . Dan itu menghindari keharusan untuk menggunakan panggilan terpisah Array.from(map).map(...)yang akan mengulang melalui iterable dua kali dan lebih buruk untuk kinerja.

Ian Storm Taylor
sumber
3

Anda dapat mengambil iterator melalui iterable, lalu mengembalikan iterator lain yang memanggil fungsi callback pemetaan pada setiap elemen yang diiterasi.

const map = (iterable, callback) => {
  return {
    [Symbol.iterator]() {
      const iterator = iterable[Symbol.iterator]();
      return {
        next() {
          const r = iterator.next();
          if (r.done)
            return r;
          else {
            return {
              value: callback(r.value),
              done: false,
            };
          }
        }
      }
    }
  }
};

// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8
MartyO256
sumber
2

Anda bisa menggunakan itiriri yang mengimplementasikan metode mirip array untuk iterable:

import { query } from 'itiriri';

let m = new Map();
// set map ...

query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();
dimadeveatii
sumber
Bagus! Beginilah seharusnya API JS dilakukan. Seperti biasa, Rust melakukannya dengan benar: doc.rust-lang.org/std/iter/trait.Iterator.html
domba terbang
"Seperti biasa, Rust melakukannya dengan benar" tentu ... Ada proposal standardisasi untuk semua jenis fungsi pembantu untuk antarmuka iterator github.com/tc39/proposal-iterator-helpers Anda dapat menggunakannya hari ini dengan corejs dengan mengimpor fromfn dari "core-js-pure / features / iterator" yang mengembalikan iterator "baru".
chpio
2

Kunjungi https://www.npmjs.com/package/fluent-iterable

Bekerja dengan semua iterable (Peta, fungsi generator, array) dan iterable async.

const map = new Map();
...
console.log(fluent(map).filter(..).map(..));
kataik
sumber
1

Ada proposal, yang menghadirkan beberapa fungsi pembantu ke Iterator: https://github.com/tc39/proposal-iterator-helpers ( dirender )

Anda dapat menggunakannya hari ini dengan memanfaatkan core-js:

import { from as iterFrom } from "core-js-pure/features/iterator";

// or if it's working for you:
// import iterFrom from "core-js-pure/features/iterator/from";

let m = new Map();

m.set("13", 37);
m.set("42", 42);

const arr = iterFrom(m.values())
  .map((val) => val * 2)
  .toArray();

// prints "[74, 84]"
console.log(arr);
chpio
sumber
0

Jawaban lain di sini adalah ... Aneh. Mereka tampaknya menerapkan kembali bagian dari protokol iterasi. Anda bisa melakukan ini:

function* mapIter(iterable, callback) {
  for (let x of iterable) {
    yield callback(x);
  }
}

dan jika ingin hasil yang konkret gunakan saja operator spread ....

[...iterMap([1, 2, 3], x => x**2)]
cowlicks
sumber