ecmascript - Transformando un iterador Javascript en una matriz
ecmascript 6 support (6)
Una pequeña actualización de 2019:
Ahora, Array.from parece estar disponible universalmente y, además, acepta un segundo argumento mapFn , que le impide crear una matriz intermedia. Esto básicamente se ve así:
Array.from(myMap.entries(), entry => {...});
Estoy tratando de usar el nuevo objeto Map de Javascript EC6, ya que ya es compatible con las últimas versiones de Firefox y Chrome.
Pero lo encuentro muy limitado en la programación "funcional", porque carece de métodos clásicos de mapa, filtro, etc. que funcionen bien con un par
[key, value]
.
Tiene forEach pero eso NO devuelve el resultado de devolución de llamada.
Si pudiera transformar su
map.entries()
de un MapIterator en una matriz simple, podría usar el estándar
.map
,
.filter
sin hacks adicionales.
¿Hay una forma "buena" de transformar un iterador Javascript en una matriz?
En python es tan fácil como hacer una
list(iterator)
... ¡pero
Array(m.entries())
devuelve una matriz con el iterador como primer elemento!
EDITAR
Olvidé especificar que estoy buscando una respuesta que funcione donde sea que funcione Map, lo que significa al menos Chrome y Firefox (Array.from no funciona en Chrome).
PD.
Sé que existe el fantástico wu.js pero su dependencia de traceur me desanima ...
Está buscando la nueva
función
Array.from
que convierte
Array.from
arbitrarios en instancias de matriz:
var arr = Array.from(map.entries());
Ahora es compatible con Edge, FF, Chrome y Node 4+ .
Por supuesto, podría valer la pena definir el
map
, el
filter
y métodos similares directamente en la interfaz del iterador, para que pueda evitar la asignación de la matriz.
También es posible que desee utilizar una función generadora en lugar de funciones de orden superior:
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;
}
No hay necesidad de transformar un
Map
en una
Array
.
Simplemente puede crear funciones de
map
y
filter
para objetos de
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;
}
Por ejemplo, podría agregar una explosión (es decir
!
carácter
!
) Al valor de cada entrada de un mapa cuya clave es primitiva.
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>
También puede agregar métodos de
map
y
filter
en
Map.prototype
para que se lea mejor.
Aunque generalmente no se recomienda modificar los prototipos nativos, creo que se puede hacer una excepción en el caso de
map
y
filter
para
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>
Editar:
En la respuesta de Bergi, creó un
map
genérico y funciones de generador de
filter
para todos los objetos iterables.
La ventaja de usarlos es que, dado que son funciones generadoras, no asignan objetos iterables intermedios.
Por ejemplo, mis funciones de
map
y
filter
definidas anteriormente crean nuevos objetos de
Map
.
Por lo tanto, llamar a
object.filter(primitive).map(appendBang)
crea dos nuevos objetos
Map
:
var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);
Crear objetos iterables intermedios es costoso. Las funciones del generador de Bergi resuelven este problema. No asignan objetos intermedios, pero permiten que un iterador alimente sus valores perezosamente al siguiente. Este tipo de optimización se conoce como fusión o deforestación en lenguajes de programación funcionales y puede mejorar significativamente el rendimiento del programa.
El único problema que tengo con las funciones generadoras de Bergi es que no son específicas de los objetos
Map
.
En cambio, se generalizan para todos los objetos iterables.
Por lo tanto, en lugar de llamar a las funciones de devolución de llamada con pares
(value, key)
(como esperaría al mapear sobre un
Map
), llama a las funciones de devolución de llamada con pares
(value, index)
.
De lo contrario, es una excelente solución y definitivamente recomendaría usarla sobre las soluciones que proporcioné.
Estas son las funciones específicas del generador que usaría para mapear y filtrar objetos
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;
}
Se pueden usar de la siguiente manera:
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>
Si desea una interfaz más fluida, puede hacer algo como esto:
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>
Espero que ayude.
Puede obtener la matriz de matrices (clave y valor):
[...this.state.selected.entries()] /** *(2) [Array(2), Array(2)] *0: (2) [2, true] *1: (2) [3, true] *length: 2 */
Y luego, puede obtener valores fácilmente desde adentro, como por ejemplo las claves con el iterador de mapa.
[...this.state.selected[asd].entries()].map(e=>e[0]) //(2) [2, 3]
Puede usar una biblioteca como https://www.npmjs.com/package/itiriri que implementa métodos de tipo matriz para 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
}
[...map.entries()]
o
Array.from(map.entries())
Es superfácil.
De todos modos, los iteradores carecen de reducción, filtro y métodos similares. Tienes que escribirlos por tu cuenta, ya que es más perfumante que convertir Map a array y viceversa. Pero no haga saltos Mapa -> Matriz -> Mapa -> Matriz -> Mapa -> Matriz, porque matará el rendimiento.