Преобразование итератора Javascript в массив

171

Я пытаюсь использовать новый объект Map из Javascript EC6, поскольку он уже поддерживается в последних версиях Firefox и Chrome.

Но я нахожу его очень ограниченным в «функциональном» программировании, потому что в нем отсутствуют классические методы отображения, фильтрации и т. Д., Которые бы прекрасно работали с [key, value]парой. У него есть forEach, но он НЕ возвращает результат обратного вызова.

Если бы я мог преобразовать его map.entries()из MapIterator в простой массив, я бы тогда мог использовать стандарт .map, .filterбез дополнительных хаков.

Есть ли «хороший» способ превратить Javascript Iterator в массив? В python это так же просто, как делать list(iterator)... но Array(m.entries())вернуть массив с Итератором в качестве первого элемента !!!

РЕДАКТИРОВАТЬ

Я забыл указать, что я ищу ответ, который работает везде, где работает Map, что означает, по крайней мере, Chrome и Firefox (Array.from не работает в Chrome).

PS.

Я знаю, что есть фантастический wu.js, но его зависимость от traceur отталкивает меня ...

Стефано
источник
См. Также 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функции должны знать структуру итерации объектов , которые они картирование над или фильтрацией. Подумайте об этом, в Хаскеле мы определяем разные случаи Functor. =)
Аадит М Шах
1
@ Стефано: Вы можете легко подправить это ...
Берги
1
@ Инкогнито: Хорошо, конечно, это правда, но это именно то, о чем спрашивает вопрос, а не проблема с моим ответом.
Берги
45

[...map.entries()] или Array.from(map.entries())

Это супер легко.

В любом случае - итераторам не хватает методам Reduce, Filter и подобных. Вы должны написать их самостоятельно, так как это более удобно, чем преобразование Map в массив и обратно. Но не делайте прыжков Карта -> Массив -> Карта -> Массив -> Карта -> Массив, потому что это снизит производительность.

Ginden
источник
1
Если у вас нет чего-то более существенного, это действительно должен быть комментарий. Кроме того, Array.fromуже была охвачена @Bergi.
Аадит М Шах
2
И, как я писал в своем первоначальном вопросе, [iterator]он не работает, потому что в Chrome он создает массив с одним iteratorэлементом и [...map.entries()]не является принятым синтаксисом в Chrome
Stefano
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генераторные функции для всех итерируемых объектов. Преимущество их использования состоит в том, что, поскольку они являются функциями генератора, они не выделяют промежуточные итерируемые объекты.

Например, функции my 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
@ Стефано Действительно. Я отредактировал свой ответ, чтобы показать, как добавить toArrayфункцию.
Аадит М Шах
7

Небольшое обновление с 2019 года:

Теперь Array.from кажется общедоступным и, кроме того, он принимает второй аргумент mapFn , который не позволяет ему создавать промежуточный массив. В основном это выглядит так:

Array.from(myMap.entries(), entry => {...});
nromaniv
источник
так как ответ с 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
источник
Эта библиотека выглядит потрясающе, а недостающий канал для перехода к итерациям @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]
ValRob
источник
0

Вы также можете использовать плавную итерацию для преобразования в массив:

const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
kataik
источник