将 Javascript 可迭代转换为数组

IT技术 javascript ecmascript-6
2021-01-29 07:29:59

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

但是我发现它在“函数式”编程中非常有限,因为它缺乏经典的映射、过滤器等方法,可以很好地与[key, value]一对一起工作。它有一个 forEach 但不返回回调结果。

如果我可以将它map.entries()从 MapIterator 转换为一个简单的 Array ,那么我就可以使用标准.map.filter而无需额外的技巧。

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

编辑

我忘了说明我正在寻找一个适用于 Map 任何地方的答案,这意味着至少 Chrome 和 Firefox(Array.from 在 Chrome 中不起作用)。

附注。

我知道有很棒的wu.js,但它对traceur的依赖让我失望...

6个回答

您正在寻找将任意可迭代对象转换为数组实例的新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;
}
@AaditMShah:迭代器的键是什么?当然,如果你要迭代地图,你可以定义yourTransformation = function([key, value], index) { … }
2021-03-16 07:29:59
迭代器没有键,但 aMap有键值对。因此,以我的拙见,为迭代器定义通用mapfilter函数没有任何意义相反,每个迭代的对象应该有自己mapfilter功能。这是有道理的,因为mapandfilter是结构保留操作(可能不是filtermap肯定是),因此mapandfilter函数应该知道它们正在映射或过滤的可迭代对象的结构。想想看,在 Haskell 中,我们定义了Functor. =)
2021-03-29 07:29:59
@Incognito 好吧,当然这是真的,但这正是问题所要求的,而不是我的回答有问题。
2021-04-03 07:29:59
我希望回调接收(value, key)对而不是(value, index)对。
2021-04-04 07:29:59
@Stefano:你可以容易地填充它......
2021-04-09 07:29:59

[...map.entries()] 或者 Array.from(map.entries())

这非常容易。

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

而且,正如我在最初的问题中所写的那样,[iterator]它不起作用,因为在 Chrome 中它创建了一个包含单个iterator元素的数组,并且[...map.entries()]在 Chrome 中不是接受的语法
2021-03-30 07:29:59
除非你有更实质性的东西,否则这应该是一个评论。此外,Array.from已经被@Bergi 覆盖。
2021-04-03 07:29:59
@Stefano扩展运算符现在是 Chrome 中接受的语法
2021-04-04 07:29:59

无需将 aMap转换为Array您可以简单地对象创建mapfilter函数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;
}

例如,您可以将一个 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>


编辑:在 Bergi 的回答中,他为所有可迭代对象创建了泛型mapfilter生成器函数。使用它们的好处是因为它们是生成器函数,所以它们不分配中间可迭代对象。

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

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

创建中间可迭代对象是昂贵的。Bergi 的生成器函数解决了这个问题。它们不分配中间对象,而是允许一个迭代器将其值懒惰地提供给下一个。这种优化在函数式编程语言中被称为融合或毁林,它可以显着提高程序性能。

我对 Bergi 的生成器函数的唯一问题是它们不是特定于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>

希望有帮助。

哇,我真的很喜欢你的编辑。我最终在这里写了我自己的答案:stackoverflow.com/a/28721418/422670(添加到那里,因为这个问题已作为重复关闭)因为Array.from它在 Chrome 中不起作用(而 Map 和迭代器可以!)。但是我可以看到该方法非常相似,您可以将“toArray”函数添加到您的群组中!
2021-03-16 07:29:59
谢谢!虽然我不知道“Array.from”,但给@Bergi 留下了很好的答案标记,这是最重要的答案。你们之间的讨论也很有趣!
2021-03-30 07:29:59
@Stefano我编辑我的回答显示发生器如何使用正确的变换Map对象使用专门mapfilter功能。Bergi 的回答演示了对所有无法用于转换对象的可迭代对象的泛型mapfilter函数的使用,Map因为对象的键Map丢失了。
2021-03-30 07:29:59
@Stefano 确实如此。我编辑了我的答案以展示如何添加toArray函数。
2021-04-02 07:29:59

2019 年的小更新:

现在 Array.from 似乎是普遍可用的,此外,它接受第二个参数mapFn,这可以防止它创建中间数组。这基本上是这样的:

Array.from(myMap.entries(), entry => {...});
由于Array.from已经存在一个答案,这更适合作为评论或要求对该答案进行编辑......但谢谢!
2021-03-20 07:29:59

您可以获得数组的数组(键和值):

[...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]