Javascript 相当于 Python 的 zip 函数

IT技术 javascript python functional-programming transpose
2021-01-30 00:47:41

是否有与 Python 的 zip 函数等效的 javascript?也就是说,给定多个长度相等的数组,创建一个成对数组。

例如,如果我有三个看起来像这样的数组:

var array1 = [1, 2, 3];
var array2 = ['a','b','c'];
var array3 = [4, 5, 6];

输出数组应该是:

var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]
6个回答

2016年更新:

这是一个更时髦的 Ecmascript 6 版本:

zip= rows=>rows[0].map((_,c)=>rows.map(row=>row[c]))

插图等效。Python { zip(*args)}:

> zip([['row0col0', 'row0col1', 'row0col2'],
       ['row1col0', 'row1col1', 'row1col2']]);
[["row0col0","row1col0"],
 ["row0col1","row1col1"],
 ["row0col2","row1col2"]]

(和FizzyTea指出ES6具有可变参数的参数语法,所以下面的函数定义会像Python,但请参阅下面的免责声明......这会不会是其自身的逆所以zip(zip(x))将不等于x;但正如马特·克拉默指出的zip(...zip(...x))==x(如在常规 python 中zip(*zip(*x))==x))

替代定义等效。Python { zip}:

> zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]))
> zip( ['row0col0', 'row0col1', 'row0col2'] ,
       ['row1col0', 'row1col1', 'row1col2'] );
             // note zip(row0,row1), not zip(matrix)
same answer as above

(请注意,此时...语法可能存在性能问题,将来也可能存在,因此如果您使用带有可变参数的第二个答案,您可能需要对其进行性能测试。也就是说,它已经存在了很长时间了标准。)

如果您希望在字符串上使用它,请务必注意附录(也许现在有更好的方法可以使用 es6 迭代器来做到这一点)。


这是一个单线:

function zip(arrays) {
    return arrays[0].map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

// > zip([[1,2],[11,22],[111,222]])
// [[1,11,111],[2,22,222]]]

// If you believe the following is a valid return value:
//   > zip([])
//   []
// then you can special-case it, or just do
//  return arrays.length==0 ? [] : arrays[0].map(...)

上面假设数组的大小相等,因为它们应该是。它还假设您传入一个列表参数列表,这与参数列表是可变参数的 Python 版本不同。如果您想要所有这些“功能”,请参见下文。它只需要大约 2 行额外的代码。

以下将模拟 Pythonzip在数组大小不相等的边缘情况下行为,默默地假装数组的较长部分不存在:

function zip() {
    var args = [].slice.call(arguments);
    var shortest = args.length==0 ? [] : args.reduce(function(a,b){
        return a.length<b.length ? a : b
    });

    return shortest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222]]]

// > zip()
// []

这将模仿 Python 的itertools.zip_longest行为,插入undefined未定义数组的位置:

function zip() {
    var args = [].slice.call(arguments);
    var longest = args.reduce(function(a,b){
        return a.length>b.length ? a : b
    }, []);

    return longest.map(function(_,i){
        return args.map(function(array){return array[i]})
    });
}

// > zip([1,2],[11,22],[111,222,333])
// [[1,11,111],[2,22,222],[null,null,333]]

// > zip()
// []

如果您使用这最后两个版本(可变参数又名多参数版本),则 zip 不再是它自己的逆版本。要模仿zip(*[...])Python 中习惯用法,zip.apply(this, [...])当您想要反转 zip 函数或者想要类似地将可变数量的列表作为输入时,您需要这样做


附录

要使此句柄可迭代(例如,在 Python 中,您可以zip在字符串、范围、地图对象等上使用),您可以定义以下内容:

function iterView(iterable) {
    // returns an array equivalent to the iterable
}

但是,如果您zip按以下方式编写,则甚至不需要:

function zip(arrays) {
    return Array.apply(null,Array(arrays[0].length)).map(function(_,i){
        return arrays.map(function(array){return array[i]})
    });
}

演示:

> JSON.stringify( zip(['abcde',[1,2,3,4,5]]) )
[["a",1],["b",2],["c",3],["d",4],["e",5]]

(或者,range(...)如果您已经编写了一个 Python 风格的函数,您可以使用它。最终您将能够使用 ECMAScript 数组推导式或生成器。)

“对象 1 没有方法 'map'”可能是尝试在没有 map 方法(例如节点列表或字符串)的对象上使用它的情况,该方法已在本文的附录中介绍
2021-03-11 00:47:41
const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
2021-03-14 00:47:41
虽然可变参数 ES6 版本确实不保留zip(zip(x)) = x,但您仍然可以相信zip(...zip(...x)) = x.
2021-03-18 00:47:41
这对我不起作用:TypeError: Object 1 has no method 'map'
2021-04-05 00:47:41
以及用于可变参数参数和任何可迭代对象的 ES6: zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
2021-04-07 00:47:41

查看图书馆Underscore

Underscore 提供了 100 多个函数,支持您最喜欢的日常功能助手:map、filter、invoke——以及更专业的好东西:函数绑定、javascript 模板、创建快速索引、深度相等性测试等等。

– 说制作它的人

我最近开始专门将它用于该zip()功能,它给人留下了很好的第一印象。我正在使用 jQuery 和 CoffeeScript,它与它们完美搭配。下划线从他们离开的地方开始,到目前为止它并没有让我失望。哦,顺便说一下,它只缩小了 3kb。

一探究竟:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
而不是下划线,试试这个:lodash.com - 替代品,同样出色的味道,更多的功能,更多的跨浏览器一致性,更好的性能。有关说明,请参阅kitcambridge.be/blog/say-hello-to-lo-dash
2021-03-18 00:47:41
使用 Underscore 时,您会感觉更接近 Haskell 的清晰性和逻辑性。
2021-04-01 00:47:41

带有生成器的现代 ES6 示例:

function *zip (...iterables){
    let iterators = iterables.map(i => i[Symbol.iterator]() )
    while (true) {
        let results = iterators.map(iter => iter.next() )
        if (results.some(res => res.done) ) return
        else yield results.map(res => res.value )
    }
}

首先,我们得到一个可迭代列表作为iterators这通常是透明地发生的,但在这里我们明确地这样做,因为我们一步一步地产生,直到其中一个被耗尽。我们检查.some()给定数组中的任何结果(使用方法)是否已用完,如果是,我们中断 while 循环。

我们从 iterables 得到一个迭代器列表。这通常是透明地发生的,在这里我们明确地这样做,因为我们一步一步地屈服,直到其中一个被耗尽。检查数组中的任何一个(.some() 方法)是否耗尽,如果是,我们中断。
2021-03-30 00:47:41
这个答案可以使用更多的解释。
2021-04-09 00:47:41

除了 ninjagecko 出色而全面的答案之外,将两个 JS 数组压缩为“元组模拟”所需的一切是:

//Arrays: aIn, aOut
Array.prototype.map.call( aIn, function(e,i){return [e, aOut[i]];})

说明:
由于 Javascript 没有tuples类型,元组、列表和集合的函数在语言规范中不是一个高优先级。
否则,类似的行为可以通过JS >1.6 中的数组映射以直接的方式访问map实际上通常由 JS 引擎制造商在许多 > JS 1.4 引擎中实现,尽管没有指定)。
与 Python 的zip, izip,...的主要区别源于map的函数式风格,因为它map需要一个函数参数。此外,它是Array-instance的函数Array.prototype.map如果输入的额外声明是一个问题,则可以改用

例子:

_tarrin = [0..constructor, function(){}, false, undefined, '', 100, 123.324,
         2343243243242343242354365476453654625345345, 'sdf23423dsfsdf',
         'sdf2324.234dfs','234,234fsf','100,100','100.100']
_parseInt = function(i){return parseInt(i);}
_tarrout = _tarrin.map(_parseInt)
_tarrin.map(function(e,i,a){return [e, _tarrout[i]]})

结果:

//'('+_tarrin.map(function(e,i,a){return [e, _tarrout[i]]}).join('),\n(')+')'
>>
(function Number() { [native code] },NaN),
(function (){},NaN),
(false,NaN),
(,NaN),
(,NaN),
(100,100),
(123.324,123),
(2.3432432432423434e+42,2),
(sdf23423dsfsdf,NaN),
(sdf2324.234dfs,NaN),
(234,234fsf,234),
(100,100,100),
(100.100,100)

相关表现:

使用mapfor循环:

请参阅:将 [1,2] 和 [7,8] 合并为 [[1,7], [2,8]] 的最有效方法是什么

拉链测试

注意:诸如false和 之类的基本类型undefined不构成原型对象层次结构,因此不公开toString函数。因此,这些在输出中显示为空。
AsparseInt的第二个参数是基数/数字基数,要将数字转换为该基数,并且由于map将索引作为第二个参数传递给其参数函数,因此使用了包装函数。

当我尝试时,您的第一个示例说“aIn 不是函数”。如果我从数组中调用 .map 而不是原型,它会起作用:有aIn.map(function(e, i) {return [e, aOut[i]];})什么问题?
2021-03-31 00:47:41
@Noumenon,Array.prototype.map应该是Array.prototype.map.call,修复了答案。
2021-04-02 00:47:41

与其他类似 Python 的函数一起,pythonic提供一个zip函数,具有返回惰性求值的额外好处Iterator,类似于其Python 对应物的行为

import {zip, zipLongest} from 'pythonic';

const arr1 = ['a', 'b'];
const arr2 = ['c', 'd', 'e'];
for (const [first, second] of zip(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d

for (const [first, second] of zipLongest(arr1, arr2))
    console.log(`first: ${first}, second: ${second}`);
// first: a, second: c
// first: b, second: d
// first: undefined, second: e

// unzip
const [arrayFirst, arraySecond] = [...zip(...zip(arr1, arr2))];

披露我是 Pythonic 的作者和维护者