将Javascript迭代器转换为数组


171

我正在尝试使用Javascript EC6中的新Map对象,因为最新的Firefox和Chrome版本已支持该对象。

但是我发现它在“功能性”编程中非常受限制,因为它缺少经典的map,filter等方法,可以很好地与一[key, value]对配合使用。它有一个forEach,但是不返回回调结果。

如果我可以将其map.entries()从MapIterator转换为简单的Array,则可以使用standard .map.filter而无需进行其他操作。

有没有一种“好的”方法可以将Javascript迭代器转换为数组?在python中,这和操作一样简单list(iterator)...但是Array(m.entries())返回一个以Iterator作为其第一个元素的数组!

编辑

我忘了指定要查找的答案,该答案在Map工作的任何地方都有效,这意味着至少Chrome和Firefox(Array.from在Chrome中不起作用)。

PS。

我知道这里有一个很棒的wu.js,但是它对traceur的依赖使我失望...


Answers:


247

您正在寻找将任意可迭代对象转换为数组实例的新Array.from函数

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

Edge,FF,Chrome和Node 4+现在支持该功能

当然,它可能是值得来定义mapfilter并直接迭代器界面上类似的方法,这样就可以避开分配阵列。您可能还想使用生成器函数而不是高阶函数:

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)配对。
Aadit M Shah,2015年

3
@AaditMShah:迭代器的键是什么?当然,如果您要迭代地图,则可以定义yourTransformation = function([key, value], index) { … }
Bergi,2015年

迭代器没有键,但是Map有键值对。因此,以我的拙见,为迭代器定义常规mapfilter函数没有任何意义。相反,每个迭代的对象应该有自己mapfilter功能。这是有道理的,因为mapfilter是结构保留操作(也许filtermap不一定是),因此mapfilter函数应该知道它们映射或过滤的可迭代对象的结构。考虑一下,在Haskell中我们定义了的不同实例Functor。=)
Aadit M Shah 2015年

1
@Stefano:您可以轻松地进行填充
Bergi 2015年

1
@Incognito好的,可以的,但这只是问题的所在,而不是我的答案有问题。
Bergi

45

[...map.entries()] 要么 Array.from(map.entries())

超级容易。

无论如何-迭代器缺少reduce,filter和类似的方法。您必须自己编写它们,因为它比将Map转换为数组然后返回的性能更高。但是不要跳Map-> Array-> Map-> Array-> Map-> Array,因为它会降低性能。


1
除非您有更重要的内容,否则这实际上应该是评论。另外,Array.from@ Bergi已经涵盖了它。
Aadit M Shah,2015年

2
而且,正如我在原始问题中所写的那样,[iterator]它不起作用,因为在Chrome中它创建了一个包含单个iterator元素的数组,并且[...map.entries()]在Chrome中不是可接受的语法
Stefano 2015年

2
Chrome已接受@Stefano spread运算符的语法
Klesun '16

15

无需将转换MapArray。您可以简单地创建mapfilterMap对象和运行:

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

例如,您可以!在键为基元的地图的每个条目的值后面附加一个“ bang”(即字符)。

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>

您还可以添加mapfilter方法,Map.prototype以使其阅读效果更好。尽管通常不建议以修改本机的原型但我认为,一个例外可能的情况下进行map,并filterMap.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>


编辑:在贝尔吉的答案,他创建了通用mapfilter为所有可迭代对象生成器函数。使用它们的好处是,由于它们是生成器函数,因此它们不会分配中间的可迭代对象。

例如,上面定义的my mapfilter函数创建新Map对象。因此,调用将object.filter(primitive).map(appendBang)创建两个新Map对象:

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

创建中间的可迭代对象非常昂贵。Bergi的生成器功能解决了这个问题。它们不分配中间对象,但允许一个迭代器将其值延迟地馈入下一个。这种优化称为在功能编程语言中融合或砍伐森林,它可以显着提高程序性能。

我对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>

希望有帮助。


它的确谢谢!给@Bergi一个好的答案标记是因为我不知道“ Array.from”,这是最关键的答案。你们之间的讨论也很有趣!
Stefano

1
@Stefano我编辑了答案,以展示如何使用生成器来Map使用专用函数mapfilter函数正确地转换对象。Bergi的答案演示了对所有可迭代对象使用泛型mapfilter函数的方法,这些对象不可用于转换Map对象,因为对象的键Map已丢失。
Aadit M Shah,2015年

哇,我真的很喜欢你的编辑。我最终在这里写下了自己的答案:stackoverflow.com/a/28721418/422670(此问题已作为重复项被关闭,在此处添加了),因为Array.from不能在Chrome中使用(而Map和迭代器可以!)。但是我可以看到这种方法非常相似,您可以将“ toArray”函数添加到您的程序集中!
斯特凡诺

1
@Stefano确实。我编辑了答案以显示如何添加toArray功能。
Aadit M Shah,2015年

7

2019年的一个小更新:

现在Array.from似乎已普遍可用,而且它接受第二个参数mapFn,这阻止了它创建中间数组。基本上看起来像这样:

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

由于答案Array.from已经存在,因此更适合作为对该答案的评论或要求对其进行编辑...但是,谢谢!
Stefano

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
}

这个lib看起来很棒,并且缺少跳转到iterables的管道@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]

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.