Javascript equivalent of Python's zip function

前端 未结 18 1509
天命终不由人
天命终不由人 2020-11-21 07:40

Is there a javascript equivalent of Python\'s zip function? That is, given multiple arrays of equal lengths create an array of pairs.

For instance, if I have three

相关标签:
18条回答
  • 2020-11-21 07:57

    ES2020 shortest variant:

    function * zip(arr1, arr2, i = 0) {
      while(arr1[i]) yield [ arr1[i], arr2[i++] ];
    }
    
    [ ...zip(arr1, arr2) ]  // result
    
    0 讨论(0)
  • 2020-11-21 08:00

    2016 update:

    Here's a snazzier Ecmascript 6 version:

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

    Illustration equiv. to Python{zip(*args)}:

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

    (and FizzyTea points out that ES6 has variadic argument syntax, so the following function definition will act like python, but see below for disclaimer... this will not be its own inverse so zip(zip(x)) will not equal x; though as Matt Kramer points out zip(...zip(...x))==x (like in regular python zip(*zip(*x))==x))

    Alternative definition equiv. to 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
    

    (Do note that the ... syntax may have performance issues at this time, and possibly in the future, so if you use the second answer with variadic arguments, you may want to perf test it.)


    Here's a oneliner:

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

    The above assumes that the arrays are of equal size, as they should be. It also assumes you pass in a single list of lists argument, unlike Python's version where the argument list is variadic. If you want all of these "features", see below. It takes just about 2 extra lines of code.

    The following will mimic Python's zip behavior on edge cases where the arrays are not of equal size, silently pretending the longer parts of arrays don't exist:

    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()
    // []
    

    This will mimic Python's itertools.zip_longest behavior, inserting undefined where arrays are not defined:

    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()
    // []
    

    If you use these last two version (variadic aka. multiple-argument versions), then zip is no longer its own inverse. To mimic the zip(*[...]) idiom from Python, you will need to do zip.apply(this, [...]) when you want to invert the zip function or if you want to similarly have a variable number of lists as input.


    addendum:

    To make this handle any iterable (e.g. in Python you can use zip on strings, ranges, map objects, etc.), you could define the following:

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

    However if you write zip in the following way, even that won't be necessary:

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

    Demo:

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

    (Or you could use a range(...) Python-style function if you've written one already. Eventually you will be able to use ECMAScript array comprehensions or generators.)

    0 讨论(0)
  • 2020-11-21 08:05

    Check out the library Underscore.

    Underscore provides over 100 functions that support both your favorite workaday functional helpers: map, filter, invoke — as well as more specialized goodies: function binding, javascript templating, creating quick indexes, deep equality testing, and so on.

    – Say the people who made it

    I recently started using it specifically for the zip() function and it has left a great first impression. I am using jQuery and CoffeeScript, and it just goes perfectly with them. Underscore picks up right where they leave off and so far it hasn't let me down. Oh by the way, it's only 3kb minified.

    Check it out:

    _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
    // returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
    
    0 讨论(0)
  • 2020-11-21 08:11

    Not built-in to Javascript itself. Some of the common Javascript frameworks (such as Prototype) provide an implementation, or you can write your own.

    0 讨论(0)
  • 2020-11-21 08:11

    You could reduce the array of arrays and map new array by taking the result of the index of the inner array.

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

    0 讨论(0)
  • 2020-11-21 08:12

    You can make utility function by using 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]]

    However, in above solution length of the first array defines the length of the output array.

    Here is the solution in which you have more control over it. It's bit complex but worth it.

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

    0 讨论(0)
提交回复
热议问题