Перетворення ітератора Javascript у масив


171

Я намагаюся використовувати новий об’єкт Map від Javascript EC6, оскільки він уже підтримується в останніх версіях Firefox та Chrome.

Але я вважаю це дуже обмеженим у "функціональному" програмуванні, тому що йому не вистачає класичних методів карти, фільтра тощо [key, value]. Він має forEach, але це НЕ повертає результат зворотного виклику.

Якби я міг перетворити його map.entries()з MapIterator у простий масив, я міг би використати стандарт .map, .filterбез додаткових хак.

Чи є "хороший" спосіб перетворити ітератор Javascript в масив? У python це так просто, як це зробити list(iterator)... але Array(m.entries())повернути масив з Iterator як його перший елемент !!!

EDIT

Я забув вказати, шукаю відповідь, яка працює там, де працює Map, що означає принаймні Chrome і Firefox (Array.from не працює в Chrome).

PS.

Я знаю, що тут є фантастичний wu.js, але його залежність від калькулятора віддає мене ...


Дивіться також stackoverflow.com/q/27612713/1460043
user1460043

Відповіді:


247

Ви шукаєте нову Array.fromфункцію, яка перетворює довільні ітерабелі в екземпляри масиву:

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

Зараз він підтримується в Edge, FF, Chrome та Node 4+ .

Звичайно, було б доцільно , щоб визначити map, filterі подібні методи безпосередньо на інтерфейс ітератора, так що ви можете уникнути виділення масиву. Ви також можете використовувати функцію генератора замість функцій вищого порядку:

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

Я б очікував, що зворотний дзвінок отримає (value, key)пари, а не (value, index)пари.
Аадіт М Шах

3
@AaditMShah: Що є ключем ітератора? Звичайно, якщо ви складете карту, ви можете визначитиyourTransformation = function([key, value], index) { … }
Бергі,

У ітератора немає ключа, але у Mapнього є пари ключових значень. Отже, на мою скромну думку, не має сенсу визначати загальні mapта filterфункції для ітераторів. Замість цього кожен ітерацію об'єкт повинен мати свої власні mapі filterфункції. Це має сенс , так mapі filterє структурою зберігаючих операцій (можливо , НЕ filterале , mapзвичайно , є) і , отже, mapі filterфункції повинні знати структуру ітерації об'єктів , які вони картування над або фільтрацією. Подумайте про це, в Haskell ми визначаємо різні екземпляри Functor. =)
Аадіт М Шах

1
@Stefano: Ви можете легко прорізати це
Бергі,

1
@ Incognito Ага, звичайно, це правда, але це саме те, про що задається питання, а не проблема з моєю відповіддю.
Бергі

45

[...map.entries()] або Array.from(map.entries())

Це дуже просто.

У будь-якому випадку - ітераторам не вистачає методів зменшення, фільтрації та подібних методів. Ви повинні написати їх самостійно, оскільки це більш сприятливо, ніж перетворення Map у масив та назад. Але не робіть стрибків Map -> Array -> Map -> Array -> Map -> Array, оскільки це знищить продуктивність.


1
Якщо у вас є щось більш суттєве, це дійсно повинен бути коментарем. Крім того, Array.fromвже висвітлював @Bergi.
Аадіт М Шах

2
І, як я писав у своєму первісному запитанні, [iterator]це не працює, оскільки в Chrome він створює масив з єдиним iteratorелементом у ньому, і [...map.entries()]не є прийнятим синтаксисом у Chrome
Стефано,

2
Оператор розповсюдження @Stefano тепер прийняв синтаксис у Chrome
Klesun

15

Немає необхідності перетворювати на Mapв Array. Ви можете просто створити mapта filterфункціонувати для Mapоб'єктів:

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

Наприклад, ви можете додати чуб (тобто !символ) до значення кожного запису карти, ключ якого є примітивом.

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>

Ви також можете додати mapта filterметоди, Map.prototypeщоб покращити читання. Хоча, як правило, не рекомендується змінювати власні прототипи, але я вважаю, що виняток може бути зроблений у випадку mapі filterдля 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>


Редагувати: У відповіді Берджі він створив загальне mapіfilter Берджі генераторну функції для всіх ітерабельних об'єктів. Перевага їх використання полягає в тому, що оскільки вони є генераторними функціями, вони не виділяють проміжні ітерабельні об'єкти.

Наприклад, мої mapта filterвизначені вище функції створюють нові Mapоб'єкти. Отже, виклик object.filter(primitive).map(appendBang)створює два нові Mapоб'єкти:

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

Створення проміжних ітерабельних об'єктів коштує дорого. Генераторські функції Бергі вирішують цю проблему. Вони не виділяють проміжні об'єкти, але дозволяють одному ітератору ліниво подавати свої значення наступному. Цей вид оптимізації відомий як синтез чи вирубка лісів у функціональних мовах програмування, і це може значно підвищити продуктивність програми.

Єдина проблема, що виникає з генераторськими функціями Бергі, - це те, що вони не характерні для Mapоб'єктів. Натомість вони узагальнені для всіх ітерабельних об’єктів. Отже, замість виклику функцій зворотного виклику (value, key)парами (як я б очікував при зіставленні через a Map), він викликає функції зворотного виклику за допомогою(value, index) парами. В іншому випадку це відмінне рішення, і я б точно рекомендував використовувати його над рішеннями, які я надав.

Отже, це специфічні функції генератора, які я використовував би для відображення та фільтрації Mapоб'єктів:

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

Їх можна використовувати наступним чином:

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>

Якщо ви хочете більш вільний інтерфейс, ви можете зробити щось подібне:

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>

Сподіваюся, що це допомагає.


це дякує! Даючи хороший знак відповіді @Bergi, хоча я не знав "Array.from", і це найбільше на те, що відповісти. Дуже цікава дискусія і між вами!
Стефано

1
@Stefano Я відредагував свою відповідь, щоб показати, як генератори можуть використовуватися для правильної трансформації Mapоб'єктів за допомогою спеціалізованих mapта filterфункцій. Відповідь Берджі демонструє використання загальних mapта filterфункцій для всіх ітерабельних об'єктів, які не можна використовувати для перетворення Mapоб’єктів, оскільки ключі Mapоб'єкта втрачені.
Аадіт М Шах

Нічого собі, мені дуже подобається ваша редакція. Я закінчив написати тут свою власну відповідь: stackoverflow.com/a/28721418/422670 (доданий там, оскільки це питання було закрито як дублікат), оскільки Array.fromце не працює в Chrome (у той час як Map і ітератори роблять!). Але я можу побачити, що підхід дуже схожий, і ви можете просто додати функцію "toArray" до своєї групи!
Стефано

1
@Stefano Дійсно. Я відредагував свою відповідь, щоб показати, як додати toArrayфункцію.
Аадіт М Шах

7

Невелике оновлення з 2019 року:

Тепер Array.from, здається, є загальнодоступним, і, крім того, він приймає другий аргумент mapFn , який заважає йому створювати проміжний масив. Це в основному виглядає так:

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

оскільки відповідь Array.fromвже існує, це більше підходить для коментаря або прохання редагувати цю відповідь ... але дякую!
Стефано

1

Ви можете використовувати бібліотеку на зразок https://www.npmjs.com/package/itiriri, яка реалізує масиви подібних методів для ітерабелів:

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
}

Ця ліб виглядає дивовижно, і пропущений канал, щоб перейти до ітерабелів @dimadeveatii - велике спасибі за його написання, я спробую скоріше :-)
Angelos Pikoulas

0

Ви можете отримати масив масивів (ключ і значення):

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

А потім ви можете легко отримати значення зсередини, як, наприклад, ключі з ітератором карти.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.