相当于Python的zip函数的Javascript


215

是否有与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]]

5
可以公平地说,我们的Python程序员“害怕”涉及循环的愚蠢方法,因为它们很慢,因此总是寻找内置的做事方法。但是在Javascript中,我们应该继续学习并编写循环,因为它们并不特别慢?
LondonRob

3
@LondonRob循环是一个循环,是否隐藏在“快速”方法后面。JavaScript的肯定已经被越来越多的支持高阶函数,引进阵列的的forEachreducemapevery,等那只是情况zip没有“做切割”(一个flatMap也没有),而不是出于性能考虑-但公平地说,.NET(3.5)两年来没有Enumerable邮编!下划线/ lodash(lodash 3.x具有延迟序列评估)之类的任何“功能性”库都将提供等效的zip函数。
user2864740 2015年

@ user2864740一个解释性的环(如在Python)总是比的机器代码回路慢。JIT编译的循环(例如在现代JS引擎中)可能接近本机CPU速度,以至于使用机器代码循环引入的增益可能会被匿名函数调用的开销所抵消。尽管如此,拥有这些内置函数并使用多个JS引擎描述“内部循环”的多种变体还是有意义的。结果可能不明显。
Tobia 2015年

Answers:


177

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;尽管正如Matt Kramer指出的那样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

(请注意,该...语法可能在此时以及将来可能会出现性能问题,因此,如果将第二个答案与可变参数一起使用,则可能要进行性能测试。)


这是一个班轮:

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(...)

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

下面的代码将模仿Python zip在数组大小不相等的极端情况下的行为,默默地假装数组的较长部分不存在:

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
这对我不起作用:类型错误:1的对象有没有方法“地图”
埃马努埃莱·鲍里尼

7
可变参数args和任何可迭代的ES6:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983年

“对象1没有方法'map'”的情况可能是试图在不具有本帖子附录中涉及的map方法(例如,节点列表或字符串)的对象上使用此方法的情况
ninjagecko

虽然确实没有保留可变的ES6版本zip(zip(x)) = x,但您仍然可以放心zip(...zip(...x)) = x
马特·克莱默

const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
КонстантинВан”,19年

34

查看库Underscore

Underscore提供了100多种功能,可同时支持您最喜欢的工作功能助手:映射,过滤器,调用—以及更专业的功能:函数绑定,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]]

3
使用Underscore时,您会更接近Haskell的清晰度和逻辑舒适度。
CamilB

12
代替下划线,请尝试以下操作:lodash.com-即插即用的替代品,相同的风味,更多的功能,更多的跨浏览器一致性,更好的性能。有关说明,请参见kitcambridge.be/blog/say-hello-to-lo-dash
Merlyn Morgan-Graham

16

除了ninjagecko出色而全面的答案外,将两个JS数组压缩为“ tuple-mimic”所需要的全部是:

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

说明:
由于Javascript没有tuples类型,因此在语言规范中,元组,列表和集合的函数并不是高优先级。
否则,可以通过JS> 1.6中的Array map以直接的方式访问类似的行为。(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)

相关性能:

使用mapover- forloops:

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

邮编测试

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


当您尝试第一个示例时,它说“ aIn不是函数”。如果我从数组而不是作为原型调用.map,它将起作用:有aIn.map(function(e, i) {return [e, aOut[i]];})什么问题吗?
Noumenon

1
@Noumenon,Array.prototype.map应该已经Array.prototype.map.call修复了答案。
用户

11

具有生成器的现代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循环。


这个答案可以使用更多的解释。
cmaher

1
我们从可迭代对象中获得迭代器列表。这通常是透明地发生的,在此我们显式地进行,因为我们一步一步产生直到其中之一用尽。检查数组中的任何一个(.some()方法)是否已用尽,如果是,我们将其中断。
Dimitris '18

11

与其他类似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的作者和维护者


7

Python具有两个功能:zip和itertools.zip_longest。在JS / ES6上的实现是这样的:

在JS / ES6上实现Python的zip

const zip = (...arrays) => {
    const length = Math.min(...arrays.map(arr => arr.length));
    return Array.from({ length }, (value, index) => arrays.map((array => array[index])));
};

结果:

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    [11, 221]
));

[[1,667,111,11]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111, 212, 323, 433, '1111']
));

[[1,667,111],[2,错误,212],[3,-378,323],['a','337',433]]

console.log(zip(
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[]

在JS / ES6上实现Python的zip_longest

https://docs.python.org/3.5/library/itertools.html?highlight=zip_longest#itertools.zip_longest

const zipLongest = (placeholder = undefined, ...arrays) => {
    const length = Math.max(...arrays.map(arr => arr.length));
    return Array.from(
        { length }, (value, index) => arrays.map(
            array => array.length - 1 >= index ? array[index] : placeholder
        )
    );
};

结果:

console.log(zipLongest(
    undefined,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[
1,667,111 ,undefined],[2,false,undefined,undefined],[3,-378,undefined,undefined],['a','337',undefined,undefined]]

console.log(zipLongest(
    null,
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[1,667,111,null],[2,false,null,null],[3,-378,null,null],['a','337',null,null]]

console.log(zipLongest(
    'Is None',
    [1, 2, 3, 'a'],
    [667, false, -378, '337'],
    [111],
    []
));

[[
1,667,111,' 不存在'],[2,假,'不存在','不存在'],[3,-378,'不存在','不存在'],['a ','337','无','无']]


4

您可以使用ES6使实用程序功能。

const zip = (arr, ...arrs) => {
  return arr.map((val, i) => arrs.reduce((a, arr) => [...a, arr[i]], [val]));
}

// example

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

console.log(zip(array1, array2));                  // [[1, 'a'], [2, 'b'], [3, 'c']]
console.log(zip(array1, array2, array3));          // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]

但是,在上述解决方案中,第一个数组的长度定义了输出数组的长度。

这是您可以对其进行更多控制的解决方案。这有点复杂,但值得。

function _zip(func, args) {
  const iterators = args.map(arr => arr[Symbol.iterator]());
  let iterateInstances = iterators.map((i) => i.next());
  ret = []
  while(iterateInstances[func](it => !it.done)) {
    ret.push(iterateInstances.map(it => it.value));
    iterateInstances = iterators.map((i) => i.next());
  }
  return ret;
}
const array1 = [1, 2, 3];
const array2 = ['a','b','c'];
const array3 = [4, 5, 6];

const zipShort = (...args) => _zip('every', args);

const zipLong = (...args) => _zip('some', args);

console.log(zipShort(array1, array2, array3)) // [[1, 'a', 4], [2, 'b', 5], [3, 'c', 6]]
console.log(zipLong([1,2,3], [4,5,6, 7]))
// [
//  [ 1, 4 ],
//  [ 2, 5 ],
//  [ 3, 6 ],
//  [ undefined, 7 ]]


4

1. Npm模块: zip-array

我发现一个npm模块可以用作python的javascript版本zip

zip- array-与Python的zip函数等效的javascript。将每个数组的值合并在一起。

https://www.npmjs.com/package/zip-array

2. tf.data.zip()在Tensorflow.js中

Tensorflow.js用户的另一个替代选择是:如果您需要zippython中的函数来使用Javascript中的tensorflow数据集,则可以tf.data.zip()在Tensorflow.js中使用。

tf.data.zip()在Tensorflow.js记录在这里


3

并非内置于JavaScript本身。一些常见的Javascript框架(例如Prototype)提供了一种实现,或者您可以编写自己的实现。


1
链接?另外,如果jQuery做到了,我会更感兴趣,因为这就是我正在使用的...
pq。


2
但是请注意,jQuery的行为与Python的行为略有不同,因为它返回一个对象,而不是数组……因此不能将两个以上的列表压缩在一起。
琥珀色

是的,作者不应该将jQuery等效。
pq。

3

像@Brandon,我建议下划线拉链功能。但是,它的作用类似于zip_longestundefined根据需要附加值,以返回最长输入长度的内容。

我使用了该mixin方法,以扩展了下划线zipShortest,其功能类似于Python,其作用是zip基于库自身的zip

您可以将以下内容添加到常见的JS代码中,然后将其作为下划线的一部分进行调用:例如_.zipShortest([1,2,3], ['a'])returns [[1, 'a']]

// Underscore library addition - zip like python does, dominated by the shortest list
//  The default injects undefineds to match the length of the longest list.
_.mixin({
    zipShortest : function() {
        var args = Array.Prototype.slice.call(arguments);
        var length = _.min(_.pluck(args, 'length')); // changed max to min
        var results = new Array(length);
        for (var i = 0; i < length; i++) {
            results[i] = _.pluck(args, "" + i);
        }
        return results;
}});

不加评论地投票?我很高兴改善这个答案,但不能没有反馈。
帕特

2

您可以通过获取内部数组的索引结果来减少数组的数组并映射新数组。

var array1 = [1, 2, 3],
    array2 = ['a','b','c'],
    array3 = [4, 5, 6],
    array = [array1, array2, array3],
    transposed = array.reduce((r, a) => a.map((v, i) => (r[i] || []).concat(v)), []);

console.log(transposed);


1

惰性生成器解决方案的

function* iter(it) {
    yield* it;
}

function* zip(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.some(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zip([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

// the only change for "longest" is some -> every

function* zipLongest(...its) {
    its = its.map(iter);
    while (true) {
        let rs = its.map(it => it.next());
        if (rs.every(r => r.done))
            return;
        yield rs.map(r => r.value);
    }
}

for (let r of zipLongest([1,2,3], [4,5,6,7], [8,9,0,11,22]))
    console.log(r.join())

这是python的经典“ n-group”习惯用法zip(*[iter(a)]*n)

triples = [...zip(...Array(3).fill(iter(a)))]

我不知道这是什么问题,我写了完全一样的一本。对我来说,感觉比所有其他方法都要好,但是也许我们俩都错了……我一直在寻找一种向其中添加流类型的方法,但是我在努力:D。
cglacet19年

0

Mochikit库提供本产品和许多其他Python相似的功能。Mochikit的开发者也是Python爱好者,因此它具有Python的通用样式,并且还将异步调用包装在类似扭曲的框架中。


0

我在纯JS中试运行,想知道上面发布的插件如何完成工作。这是我的结果。首先,我不知道这在IE等环境中的稳定性如何。这只是一个快速的模型。

init();

function init() {
    var one = [0, 1, 2, 3];
    var two = [4, 5, 6, 7];
    var three = [8, 9, 10, 11, 12];
    var four = zip(one, two, one);
    //returns array
    //four = zip(one, two, three);
    //returns false since three.length !== two.length
    console.log(four);
}

function zip() {
    for (var i = 0; i < arguments.length; i++) {
        if (!arguments[i].length || !arguments.toString()) {
            return false;
        }
        if (i >= 1) {
            if (arguments[i].length !== arguments[i - 1].length) {
                return false;
            }
        }
    }
    var zipped = [];
    for (var j = 0; j < arguments[0].length; j++) {
        var toBeZipped = [];
        for (var k = 0; k < arguments.length; k++) {
            toBeZipped.push(arguments[k][j]);
        }
        zipped.push(toBeZipped);
    }
    return zipped;
}

它不是防弹的,但仍然很有趣。


jsfiddle看起来不错。有一个TidyUp按钮!“运行”按钮未在“结果”面板中显示console.log输出。为什么?
pq。

它(console.log)需要运行Firebug之类的东西。只需切换console.log到即可alert

那么结果窗格是什么?
pq。

它显示了小提琴的HTML。在这种情况下,我只是直接编写JS。这是使用document.write() jsfiddle.net/PyTWw/5

-1

这使Ddi的基于迭代器的答案了一行:

function* zip(...toZip) {
  const iterators = toZip.map((arg) => arg[Symbol.iterator]());
  const next = () => toZip = iterators.map((iter) => iter.next());
  while (next().every((item) => !item.done)) {
    yield toZip.map((item) => item.value);
  }
}

-1

如果您对ES6满意,请执行以下操作:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
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.