Mengubah iterator Javascript menjadi sebuah array


171

Saya mencoba menggunakan objek Peta baru dari Javascript EC6, karena sudah didukung di versi Firefox dan Chrome terbaru.

Tapi saya menemukan itu sangat terbatas dalam pemrograman "fungsional", karena tidak memiliki peta klasik, filter dll metode yang akan bekerja dengan baik dengan [key, value]pasangan. Memiliki forEach tapi itu TIDAK mengembalikan hasil panggilan balik.

Jika saya bisa mentransformasikannya map.entries()dari MapIterator menjadi Array sederhana maka saya bisa menggunakan standar .map, .filtertanpa peretasan tambahan.

Apakah ada cara "baik" untuk mengubah Javascript Iterator menjadi Array? Dengan python semudah melakukannya list(iterator)... tapi Array(m.entries())kembalikan array dengan Iterator sebagai elemen pertama !!!

EDIT

Saya lupa menentukan Saya sedang mencari jawaban yang berfungsi di mana pun Peta berfungsi, yang berarti setidaknya Chrome dan Firefox (Array.from tidak berfungsi di Chrome).

PS.

Saya tahu ada wu.js fantastis tetapi ketergantungannya pada traceur membuat saya pergi ...


Jawaban:


247

Anda mencari Array.fromfungsi baru yang mengubah iterables sewenang-wenang menjadi instance array:

var arr = Array.from(map.entries());

Sekarang didukung di Edge, FF, Chrome dan Node 4+ .

Tentu saja, mungkin layak untuk didefinisikan map, filterdan metode serupa langsung pada antarmuka iterator, sehingga Anda dapat menghindari alokasi array. Anda juga mungkin ingin menggunakan fungsi generator alih-alih fungsi tingkat tinggi:

function* map(iterable) {
    var i = 0;
    for (var item of iterable)
        yield yourTransformation(item, i++);
}
function* filter(iterable) {
    var i = 0;
    for (var item of iterable)
        if (yourPredicate(item, i++))
             yield item;
}

Saya harapkan panggilan balik untuk menerima (value, key)pasangan dan bukan (value, index)pasangan.
Aadit M Shah

3
@AaditMShah: Apa kunci iterator? Tentu saja, jika Anda akan mengulangi peta, Anda bisa mendefinisikanyourTransformation = function([key, value], index) { … }
Bergi

Sebuah iterator tidak memiliki kunci tetapi Mapmemiliki pasangan nilai kunci. Oleh karena itu, menurut pendapat saya, tidak masuk akal untuk mendefinisikan fungsi umum mapdan filteruntuk iterator. Sebaliknya, setiap objek yang diulang harus memiliki fungsi mapdan filterfungsinya masing-masing . Ini masuk akal karena mapdan filteryang struktur operasi melestarikan (mungkin tidak filtertapi mappasti) dan karenanya mapdan filterfungsi harus tahu struktur dari objek iterable bahwa mereka pemetaan atas atau penyaringan. Pikirkan tentang itu, di Haskell kita mendefinisikan berbagai contoh Functor. =)
Aadit M Shah

1
@Stefano: Anda dapat shim dengan mudah ...
Bergi

1
@Incognito Ah ok, tentu itu benar, tapi itu hanya pertanyaannya, bukan masalah dengan jawaban saya.
Bergi

45

[...map.entries()] atau Array.from(map.entries())

Sangat mudah.

Bagaimanapun - iterators kurang mengurangi, menyaring, dan metode serupa. Anda harus menulis sendiri, karena ini lebih perfomant daripada mengubah Peta ke array dan kembali. Tapi jangan lakukan lompatan Map -> Array -> Map -> Array -> Map -> Array, karena itu akan mematikan kinerja.


1
Kecuali Anda memiliki sesuatu yang lebih substansial, ini harus menjadi komentar. Selain itu, Array.fromsudah dicakup oleh @Bergi.
Aadit M Shah

2
Dan, seperti yang saya tulis dalam pertanyaan asli saya, [iterator]tidak berfungsi karena di Chrome Chrome menciptakan array dengan iteratorelemen tunggal di dalamnya, dan [...map.entries()]bukan sintaks yang diterima di Chrome
Stefano

2
@Stefano operator spread sekarang menerima sintaks di Chrome
Klesun

15

Tidak perlu mengubah Mapmenjadi Array. Anda cukup membuat mapdan filterfungsi untuk Mapobjek:

function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self);

    return result;
}

Misalnya, Anda bisa menambahkan bang (yaitu !karakter) dengan nilai setiap entri peta yang kuncinya adalah primitif.

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = map(appendBang, filter(primitive, object));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
}
</script>

Anda juga dapat menambahkan mapdan filtermetode Map.prototypeuntuk membuatnya lebih baik dibaca. Meskipun umumnya tidak disarankan untuk memodifikasi prototipe asli, namun saya percaya bahwa pengecualian dapat dibuat dalam kasus mapdan filteruntuk Map.prototype:

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = object.filter(primitive).map(appendBang);

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
Map.prototype.map = function (functor, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
};

Map.prototype.filter = function (predicate, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
};
</script>


Sunting: Dalam jawaban Bergi, ia menciptakan fungsi generik mapdan filtergenerator untuk semua objek yang dapat diubah. Keuntungan menggunakan mereka adalah karena mereka adalah fungsi generator, mereka tidak mengalokasikan objek iterable menengah.

Sebagai contoh, fungsi saya mapdan yang filterdidefinisikan di atas membuat Mapobjek baru . Karenanya panggilan object.filter(primitive).map(appendBang)menciptakan dua Mapobjek baru :

var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);

Menciptakan objek yang dapat ditengah-tengah adalah mahal. Fungsi generator Bergi memecahkan masalah ini. Mereka tidak mengalokasikan objek perantara tetapi memungkinkan satu iterator untuk memberi makan nilainya dengan malas ke yang berikutnya. Jenis optimasi ini dikenal sebagai fusi atau penggundulan hutan dalam bahasa pemrograman fungsional dan ini dapat secara signifikan meningkatkan kinerja program.

Satu-satunya masalah yang saya miliki dengan fungsi generator Bergi adalah bahwa mereka tidak spesifik untuk Mapobjek. Sebaliknya, mereka digeneralisasi untuk semua objek yang dapat diubah. Oleh karena itu alih-alih memanggil fungsi callback dengan (value, key)pasangan (seperti yang saya harapkan ketika memetakan lebih dari a Map), itu memanggil fungsi callback dengan (value, index)pasangan. Kalau tidak, ini adalah solusi yang sangat baik dan saya pasti akan merekomendasikan menggunakannya atas solusi yang saya berikan.

Jadi ini adalah fungsi generator spesifik yang akan saya gunakan untuk memetakan dan memfilter Mapobjek:

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}

Mereka dapat digunakan sebagai berikut:

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = toMap(map(appendBang, filter(primitive, object.entries())));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = toArray(map(appendBang, filter(primitive, object.entries())));

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

Jika Anda ingin antarmuka yang lebih lancar maka Anda bisa melakukan sesuatu seperti ini:

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = new MapEntries(object).filter(primitive).map(appendBang).toMap();

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = new MapEntries(object).filter(primitive).map(appendBang).toArray();

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
MapEntries.prototype = {
    constructor: MapEntries,
    map: function (functor, self) {
        return new MapEntries(map(functor, this.entries, self), true);
    },
    filter: function (predicate, self) {
        return new MapEntries(filter(predicate, this.entries, self), true);
    },
    toMap: function () {
        return toMap(this.entries);
    },
    toArray: function () {
        return toArray(this.entries);
    }
};

function MapEntries(map, entries) {
    this.entries = entries ? map : map.entries();
}

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

Semoga itu bisa membantu.


terima kasih! Memberikan tanda jawaban yang bagus untuk @Bergi karena saya tidak tahu "Array.from" dan itulah jawaban yang paling langsung. Diskusi yang sangat menarik di antara Anda juga!
Stefano

1
@Stefano Saya mengedit jawaban saya untuk menunjukkan bagaimana generator dapat digunakan untuk mengubah Mapobjek dengan benar menggunakan fungsi mapdan filterfungsi khusus. Jawaban Bergi menunjukkan penggunaan generik mapdan filterfungsi untuk semua objek yang dapat diubah yang tidak dapat digunakan untuk mengubah Mapobjek karena kunci Mapobjek hilang.
Aadit M Shah

Wow, saya sangat suka hasil edit Anda. Saya akhirnya menulis jawaban saya sendiri di sini: stackoverflow.com/a/28721418/422670 (ditambahkan di sana karena pertanyaan ini telah ditutup sebagai duplikat) karena Array.fromtidak berfungsi di Chrome (sementara Peta dan iterator melakukannya!). Tapi saya bisa melihat pendekatannya sangat mirip dan Anda bisa menambahkan fungsi "toArray" ke grup Anda!
Stefano

1
@Stano Memang. Saya mengedit jawaban saya untuk menunjukkan cara menambahkan toArrayfungsi.
Aadit M Shah

7

Pembaruan kecil dari 2019:

Sekarang Array.from tampaknya tersedia secara universal, dan, lebih jauh lagi, ia menerima argumen mapFn kedua , yang mencegahnya membuat array perantara. Ini pada dasarnya terlihat seperti ini:

Array.from(myMap.entries(), entry => {...});

karena jawaban dengan Array.fromsudah ada, ini lebih cocok untuk dijadikan komentar atau edit yang diminta untuk jawaban itu ... tapi terima kasih!
Stefano

1

Anda bisa menggunakan perpustakaan seperti https://www.npmjs.com/package/itiriri yang mengimplementasikan metode mirip array untuk iterables:

import { query } from 'itiriri';

const map = new Map();
map.set(1, 'Alice');
map.set(2, 'Bob');

const result = query(map)
  .filter([k, v] => v.indexOf('A') >= 0)
  .map([k, v] => `k - ${v.toUpperCase()}`);

for (const r of result) {
  console.log(r); // prints: 1 - ALICE
}

Lib ini terlihat luar biasa & saluran yang hilang untuk melompat ke iterables @dimadeveatii - terima kasih banyak untuk menulisnya, saya akan segera mencobanya :-)
Angelos Pikoulas

0

Anda bisa mendapatkan array array (kunci dan nilai):

[...this.state.selected.entries()]
/**
*(2) [Array(2), Array(2)]
*0: (2) [2, true]
*1: (2) [3, true]
*length: 2
*/

Dan kemudian, Anda dapat dengan mudah mendapatkan nilai dari dalam, seperti misalnya tombol dengan iterator peta.

[...this.state.selected[asd].entries()].map(e=>e[0])
//(2) [2, 3]

0

Anda juga dapat menggunakan fasih-iterable untuk mengubah ke array:

const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.