将 Javascript 可迭代对象转换为数组
Transforming a Javascript iterable into an array
我正在尝试使用来自 Javascript EC6 的新 Map 对象,因为它已在最新的 Firefox 和 Chrome 版本中得到支持。
但我发现它在 "functional" 编程中非常有限,因为它缺少经典的映射、过滤器等可以很好地与 [key, value]
对一起工作的方法。它有一个 forEach 但不 return 回调结果。
如果我可以将它的 map.entries()
从 MapIterator 转换成一个简单的数组,那么我就可以使用标准的 .map
、.filter
而无需额外的修改。
是否有 "good" 方法将 Javascript 迭代器转换为数组?
在 python 中,它与 list(iterator)
一样简单...但是 Array(m.entries())
return 一个以迭代器作为第一个元素的数组!!!
编辑
我忘了说明我正在寻找一个适用于 Map 的任何地方的答案,这意味着至少 Chrome 和 Firefox(Array.from 在 Chrome 中不起作用)。
PS.
我知道有很棒的 wu.js 但它对 traceur 的依赖让我很反感...
您正在寻找新的 Array.from
function,它将任意可迭代对象转换为数组实例:
var arr = Array.from(map.entries());
现在是supported in Edge, FF, Chrome and Node 4+。
当然,直接在iterator接口上定义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;
}
无需将 Map
转换为 Array
。您可以简单地为 Map
个对象创建 map
和 filter
函数:
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.prototype
上添加 map
和 filter
方法以使其更好地阅读。虽然通常不建议修改本机原型,但我认为在 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>
编辑: 在 Bergi 的回答中,他为所有可迭代对象创建了通用的 map
和 filter
生成器函数。使用它们的好处是因为它们是生成器函数,所以它们不会分配中间可迭代对象。
例如,我上面定义的 map
和 filter
函数创建了新的 Map
对象。因此调用 object.filter(primitive).map(appendBang)
会创建两个新的 Map
对象:
var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);
创建中间可迭代对象非常昂贵。 Bergi 的生成器函数解决了这个问题。它们不分配中间对象,但允许一个迭代器将其值惰性地提供给下一个迭代器。这种优化在函数式编程语言中被称为fusion or deforestation,它可以显着提高程序性能。
我对 Bergi 的生成器函数的唯一问题是它们不特定于 Map
个对象。相反,它们被泛化为所有可迭代对象。因此,它不是使用 (value, key)
对调用回调函数(正如我在 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>
希望对您有所帮助。
[...map.entries()]
或 Array.from(map.entries())
超级简单。
无论如何 - 迭代器缺少 reduce、filter 和类似的方法。您必须自己编写它们,因为它比将 Map 转换为数组再返回的性能更高。但是不要跳转 Map -> Array -> Map -> Array -> Map -> Array,因为它会降低性能。
您可以使用像 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
}
2019 年的小更新:
现在 Array.from 似乎普遍可用,而且,它接受第二个参数 mapFn,这会阻止它创建中间数组。这基本上是这样的:
Array.from(myMap.entries(), entry => {...});
您可以获得数组的数组(键和值):
[...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]
也可以用fluent-iterable转成数组:
const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
使用 iter-ops 库,您可以直接在 Map
或任何可迭代对象上使用 filter
和 map
操作,而无需将其转换为数组:
import {filter, map, pipe} from 'iter-ops';
const m = new Map<number, number>();
m.set(0, 12);
m.set(1, 34);
const r = pipe(
m,
filter(([key, value]) => {
// return the filter flag as required
}),
map(([key, value]) => {
// return the re-mapped value as required
})
);
console.log([...r]); //=> print all resulting values
我正在尝试使用来自 Javascript EC6 的新 Map 对象,因为它已在最新的 Firefox 和 Chrome 版本中得到支持。
但我发现它在 "functional" 编程中非常有限,因为它缺少经典的映射、过滤器等可以很好地与 [key, value]
对一起工作的方法。它有一个 forEach 但不 return 回调结果。
如果我可以将它的 map.entries()
从 MapIterator 转换成一个简单的数组,那么我就可以使用标准的 .map
、.filter
而无需额外的修改。
是否有 "good" 方法将 Javascript 迭代器转换为数组?
在 python 中,它与 list(iterator)
一样简单...但是 Array(m.entries())
return 一个以迭代器作为第一个元素的数组!!!
编辑
我忘了说明我正在寻找一个适用于 Map 的任何地方的答案,这意味着至少 Chrome 和 Firefox(Array.from 在 Chrome 中不起作用)。
PS.
我知道有很棒的 wu.js 但它对 traceur 的依赖让我很反感...
您正在寻找新的 Array.from
function,它将任意可迭代对象转换为数组实例:
var arr = Array.from(map.entries());
现在是supported in Edge, FF, Chrome and Node 4+。
当然,直接在iterator接口上定义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;
}
无需将 Map
转换为 Array
。您可以简单地为 Map
个对象创建 map
和 filter
函数:
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.prototype
上添加 map
和 filter
方法以使其更好地阅读。虽然通常不建议修改本机原型,但我认为在 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>
编辑: 在 Bergi 的回答中,他为所有可迭代对象创建了通用的 map
和 filter
生成器函数。使用它们的好处是因为它们是生成器函数,所以它们不会分配中间可迭代对象。
例如,我上面定义的 map
和 filter
函数创建了新的 Map
对象。因此调用 object.filter(primitive).map(appendBang)
会创建两个新的 Map
对象:
var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);
创建中间可迭代对象非常昂贵。 Bergi 的生成器函数解决了这个问题。它们不分配中间对象,但允许一个迭代器将其值惰性地提供给下一个迭代器。这种优化在函数式编程语言中被称为fusion or deforestation,它可以显着提高程序性能。
我对 Bergi 的生成器函数的唯一问题是它们不特定于 Map
个对象。相反,它们被泛化为所有可迭代对象。因此,它不是使用 (value, key)
对调用回调函数(正如我在 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>
希望对您有所帮助。
[...map.entries()]
或 Array.from(map.entries())
超级简单。
无论如何 - 迭代器缺少 reduce、filter 和类似的方法。您必须自己编写它们,因为它比将 Map 转换为数组再返回的性能更高。但是不要跳转 Map -> Array -> Map -> Array -> Map -> Array,因为它会降低性能。
您可以使用像 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
}
2019 年的小更新:
现在 Array.from 似乎普遍可用,而且,它接受第二个参数 mapFn,这会阻止它创建中间数组。这基本上是这样的:
Array.from(myMap.entries(), entry => {...});
您可以获得数组的数组(键和值):
[...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]
也可以用fluent-iterable转成数组:
const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
使用 iter-ops 库,您可以直接在 Map
或任何可迭代对象上使用 filter
和 map
操作,而无需将其转换为数组:
import {filter, map, pipe} from 'iter-ops';
const m = new Map<number, number>();
m.set(0, 12);
m.set(1, 34);
const r = pipe(
m,
filter(([key, value]) => {
// return the filter flag as required
}),
map(([key, value]) => {
// return the re-mapped value as required
})
);
console.log([...r]); //=> print all resulting values