Chuyển đổi một trình vòng lặp Javascript thành một mảng


171

Tôi đang cố gắng sử dụng đối tượng Bản đồ mới từ Javascript EC6, vì nó đã được hỗ trợ trong các phiên bản Firefox và Chrome mới nhất.

Nhưng tôi thấy nó rất hạn chế trong lập trình "chức năng", vì nó thiếu các phương pháp bản đồ cổ điển, bộ lọc, vv sẽ hoạt động tốt với một [key, value]cặp. Nó có forEach nhưng điều đó KHÔNG trả về kết quả gọi lại.

Nếu tôi có thể biến đổi nó map.entries()từ một MapIterator thành một mảng đơn giản tôi sau đó có thể sử dụng tiêu chuẩn .map, .filterkhông có hacks bổ sung.

Có cách nào "tốt" để chuyển đổi Trình lặp Javascript thành Mảng không? Trong python, nó dễ như làm list(iterator)... nhưng Array(m.entries())trả về một mảng với Iterator là phần tử đầu tiên của nó !!!

BIÊN TẬP

Tôi quên chỉ định Tôi đang tìm câu trả lời hoạt động ở bất cứ nơi nào Map hoạt động, điều đó có nghĩa là ít nhất là Chrome và Firefox (Array.from không hoạt động trong Chrome).

Tái bút

Tôi biết có wu.js tuyệt vời nhưng sự phụ thuộc của nó vào dấu vết khiến tôi không thể ...


Câu trả lời:


247

Bạn đang tìm kiếm Array.fromhàm mới chuyển đổi các lần lặp tùy ý thành các thể hiện của mảng:

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

Nó hiện được hỗ trợ trong Edge, FF, Chrome và Node 4+ .

Tất nhiên, nó có thể là giá trị để xác định map, filtervà các phương pháp tương tự trực tiếp trên giao diện iterator, do đó bạn có thể tránh bố trí mảng. Bạn cũng có thể muốn sử dụng chức năng tạo thay vì các hàm bậc cao hơn:

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

Tôi mong đợi cuộc gọi lại nhận được (value, key)cặp và không phải (value, index)cặp.
Aadit M Shah

3
@AaditMShah: Chìa khóa của trình vòng lặp là gì? Tất nhiên, nếu bạn lặp lại một bản đồ, bạn có thể xác địnhyourTransformation = function([key, value], index) { … }
Bergi

Trình lặp không có khóa nhưng Mapkhông có cặp giá trị khóa. Do đó, theo quan điểm khiêm tốn của tôi, việc định nghĩa chung mapvà các filterchức năng cho các trình vòng lặp không có ý nghĩa gì . Thay vào đó, mỗi đối tượng lặp lại nên có chức năng mapfilterchức năng riêng . Điều này có ý nghĩa bởi vì mapfilterlà các hoạt động bảo toàn cấu trúc (có lẽ không filternhưng mapchắc chắn là như vậy) và do đó, mapvà các filterhàm nên biết cấu trúc của các đối tượng lặp mà chúng đang ánh xạ hoặc lọc. Hãy suy nghĩ về nó, trong Haskell, chúng tôi xác định các trường hợp khác nhau của Functor. =)
Aadit M Shah

1
@Stefano: Bạn có thể bắt chước một cách dễ dàng
Bergi

1
@Incognito Ah ok, chắc chắn đó là sự thật, nhưng đó chỉ là những gì câu hỏi đang hỏi, không phải là vấn đề với câu trả lời của tôi.
Bergi

45

[...map.entries()] hoặc là Array.from(map.entries())

Nó siêu dễ.

Dù sao - iterators thiếu giảm, bộ lọc và các phương thức tương tự. Bạn phải tự viết chúng, vì nó hoàn hảo hơn là chuyển đổi Bản đồ thành mảng và ngược lại. Nhưng đừng thực hiện nhảy Bản đồ -> Mảng -> Bản đồ -> Mảng -> Bản đồ -> Mảng, vì nó sẽ giết chết hiệu suất.


1
Trừ khi bạn có một cái gì đó quan trọng hơn, đây thực sự nên là một nhận xét. Ngoài ra, Array.fromđã được @Bergi bảo hiểm.
Aadit M Shah

2
Và, như tôi đã viết trong câu hỏi ban đầu của mình, [iterator]không hoạt động vì trong Chrome, nó tạo ra một mảng có một thành iteratorphần duy nhất trong đó và [...map.entries()]không phải là một cú pháp được chấp nhận trong Chrome
Stefano

2
Toán tử lây lan @Stefano hiện được chấp nhận cú pháp trong Chrome
Klesun

15

Không cần phải chuyển đổi Mapthành một Array. Bạn chỉ có thể tạo mapvà các filterchức năng cho Mapcác đối tượng:

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

Ví dụ: bạn có thể nối một tiếng nổ (tức là !ký tự) vào giá trị của mỗi mục trên bản đồ có khóa là nguyên thủy.

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>

Bạn cũng có thể thêm mapfiltercác phương thức Map.prototypeđể làm cho nó đọc tốt hơn. Mặc dù nói chung không nên sửa đổi các nguyên mẫu gốc nhưng tôi tin rằng một ngoại lệ có thể được tạo ra trong trường hợp mapfiltercho 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>


Chỉnh sửa: Trong câu trả lời của Bergi, ông đã tạo ra các hàm chung mapvà trình filtertạo cho tất cả các đối tượng lặp lại. Ưu điểm của việc sử dụng chúng là vì chúng là các hàm tạo, chúng không phân bổ các đối tượng lặp trung gian.

Ví dụ, hàm của tôi mapvà các filterhàm được định nghĩa ở trên tạo các Mapđối tượng mới . Do đó, việc gọi object.filter(primitive).map(appendBang)sẽ tạo ra hai Mapđối tượng mới :

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

Tạo các đối tượng lặp trung gian là tốn kém. Các chức năng tạo của Bergi giải quyết vấn đề này. Họ không phân bổ các đối tượng trung gian nhưng cho phép một trình vòng lặp đưa các giá trị của nó một cách lười biếng sang kế tiếp. Loại tối ưu hóa này được gọi là hợp nhất hoặc phá rừng trong các ngôn ngữ lập trình chức năng và nó có thể cải thiện đáng kể hiệu suất chương trình.

Vấn đề duy nhất tôi gặp phải với các hàm tạo của Bergi là chúng không dành riêng cho Mapcác đối tượng. Thay vào đó, chúng được khái quát cho tất cả các đối tượng lặp lại. Do đó, thay vì gọi các hàm gọi lại bằng (value, key)các cặp (như tôi mong đợi khi ánh xạ qua a Map), nó gọi các hàm gọi lại bằng (value, index)các cặp. Mặt khác, đó là một giải pháp tuyệt vời và tôi chắc chắn sẽ khuyên bạn nên sử dụng nó trên các giải pháp mà tôi đã cung cấp.

Vì vậy, đây là các hàm tạo cụ thể mà tôi sẽ sử dụng để ánh xạ và lọc Mapcác đối tượng:

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

Chúng có thể được sử dụng như sau:

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>

Nếu bạn muốn một giao diện trôi chảy hơn thì bạn có thể làm một cái gì đó như thế này:

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>

Mong rằng sẽ giúp.


nó cảm ơn Tuy nhiên, việc đưa ra câu trả lời tốt cho @Bergi vì tôi không biết "Array.from" và đó là câu trả lời chính xác nhất. Cuộc thảo luận rất thú vị giữa bạn quá!
Stefano

1
@Stefano Tôi đã chỉnh sửa câu trả lời của mình để chỉ ra cách các trình tạo có thể được sử dụng để chuyển đổi chính xác Mapcác đối tượng bằng cách sử dụng các chức năng mapvà chuyên dụng filter. Câu trả lời của Bergi thể hiện việc sử dụng chung mapfilterhàm cho tất cả các đối tượng có thể lặp lại không thể được sử dụng để chuyển đổi Mapcác đối tượng vì các khóa của Mapđối tượng bị mất.
Aadit M Shah

Wow, tôi thực sự thích chỉnh sửa của bạn. Cuối cùng tôi đã viết câu trả lời của riêng mình ở đây: stackoverflow.com/a/28721418/422670 (được thêm vào đó vì câu hỏi này đã bị đóng dưới dạng trùng lặp) vì Array.fromkhông hoạt động trong Chrome (trong khi Map và iterators thì có!). Nhưng tôi có thể thấy cách tiếp cận rất giống nhau và bạn chỉ cần thêm chức năng "toArray" vào bó của mình!
Stefano

1
@Stefano Thật vậy. Tôi chỉnh sửa câu trả lời của mình để hiển thị cách thêm toArraychức năng.
Aadit M Shah

7

Một bản cập nhật nhỏ từ năm 2019:

Bây giờ Array.from dường như có sẵn trên toàn cầu, và hơn nữa, nó chấp nhận một đối số thứ hai mapFn , điều này ngăn nó tạo ra một mảng trung gian. Điều này về cơ bản trông như thế này:

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

vì một câu trả lời Array.fromđã tồn tại, điều này phù hợp hơn để trở thành một bình luận hoặc yêu cầu chỉnh sửa cho câu trả lời đó ... nhưng cảm ơn!
Stefano

1

Bạn có thể sử dụng một thư viện như https://www.npmjs.com/package/itiriri thực hiện các phương thức giống như mảng cho 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 này trông tuyệt vời và ống dẫn bị mất để nhảy đến iterables @dimadeveatii - cảm ơn rất nhiều vì đã viết nó, tôi sẽ thử nó sớm :-)
Angelos Pikoulas

0

Bạn có thể nhận được mảng của mảng (khóa và giá trị):

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

Và sau đó, bạn có thể dễ dàng lấy các giá trị từ bên trong, ví dụ như các phím có trình lặp bản đồ.

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

0

Bạn cũng có thể sử dụng fluent-iterable để chuyển đổi thành mảng:

const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.