Javascript-эквивалент функции zip в Python


216

Есть ли javascript-эквивалент функции zip в Python? То есть, учитывая несколько массивов одинаковой длины, создайте массив пар.

Например, если у меня есть три массива, которые выглядят так:

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 определенно получал больше поддержки функций высшего порядка, с введением массива forEach, reduce, map, everyи т.д. Это был как раз тот случай , что zip«не делают разрез» (а flatMapтакже отсутствует), а не из соображений производительности - но честно говоря, в .NET (3.5) не было Zip на Enumerable в течение пары лет! Любая «функциональная» библиотека, такая как underscore / lodash (lodash 3.x имеет ленивую оценку последовательности), предоставит эквивалентную функцию zip.
user2864740

@ user2864740 Интерпретируемый цикл (например, в Python) всегда будет намного медленнее цикла машинного кода. Цикл, скомпилированный JIT (например, в современных механизмах JS), может приблизиться к собственной скорости ЦП, настолько, что усиление, полученное при использовании цикла машинного кода, может быть компенсировано накладными расходами вызова анонимной функции. Тем не менее, имеет смысл иметь эти встроенные функции и профилировать несколько вариантов ваших «внутренних циклов» с несколькими движками JS. Результаты не могут быть очевидными.
Tobia

Ответы:


178

Обновление 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(например, в штатном питоне 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(...)

Выше предполагается, что массивы имеют одинаковый размер, как и должно быть. Также предполагается, что вы передаете один аргумент list списков, в отличие от версии Python, где список аргументов является вариативным. Если вы хотите все эти «функции», см. Ниже. Это займет всего около 2 дополнительных строк кода.

Следующее будет имитировать zipповедение Python в крайних случаях, когда массивы не имеют одинаковый размер, молча притворяясь, что более длинные части массивов не существуют:

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

Это будет имитировать itertools.zip_longestповедение Python , вставляя 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()
// []

Если вы используете эти две последние версии (variadic, то есть версии с несколькими аргументами), то 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
Это не работает для меня: TypeError: У объекта 1 нет метода 'map'
Эмануэле Паолини

7
И ES6 для переменных аргументов и любых повторяющихся:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983

«У объекта 1 нет метода« карта »», вероятно, это случай попытки использовать его на объекте, у которого нет метода карты (такого как список узлов или строка), о котором говорилось в дополнении к этому посту.
ninjagecko

Хотя версия Variadic ES6 не сохранилась zip(zip(x)) = x, вы все равно можете быть уверены в этом zip(...zip(...x)) = x.
Мэтт Крамер

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

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 для описания.
Мерлин Морган-Грэм

16

В дополнение к превосходному и исчерпывающему ответу 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 двигателях, несмотря на то, что не указано).
Основное отличие в Питон zip, izip... Результаты mapфункционального стиля «S, так как mapтребует функцию-аргумента. Кроме того, это функция Arrayэкземпляра. 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)

Связанная производительность:

Использование mapболее for-loops:

См .: Какой самый эффективный способ объединения [1,2] и [7,8] в [[1,7], [2,8]]

почтовые тесты

Примечание: базовые типы, такие как falseи undefinedне обладают прототипной иерархией объектов и, следовательно, не предоставляют toStringфункции. Следовательно, они показаны как пустые в выходных данных.
Поскольку parseIntвторым аргументом является основание / число основание, в которое нужно преобразовать число, и, поскольку mapиндекс передает его в качестве второго аргумента своей функции-аргумента, используется функция-обертка.


Ваш первый пример говорит "aIn не функция", когда я пытаюсь это сделать. Это работает, если я вызываю .map из массива, а не как прототип: aIn.map(function(e, i) {return [e, aOut[i]];})что не так?
Нумен

1
@ Нумен, 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 ()) в массиве, и мы прерываем его, если это так.
Димитрис

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

Питон имеет две функции: zip и itertools.zip_longest. Реализация на JS / ES6 выглядит так:

Реализация Python`s Zip на JS / ES6

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, false, 212], [3, -378, 323], ['a', '337', 433]]

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

[]

Реализация Python's zip_longest на JS / ES6

( 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, ноль], [2, ложь, ноль, ноль], [3, -378, ноль, ноль], ['a', '337', ноль, ноль]]

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 - javascript-эквивалент функции zip в Python. Объединяет значения каждого из массивов.

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

2. tf.data.zip()в Tensorflow.js

Другой альтернативный выбор для пользователей Tensorflow.js: если вам нужна zipфункция в python для работы с наборами данных tenorflow в Javascript, вы можете использовать tf.data.zip()в Tensorflow.js.

tf.data.zip () в Tensorflow.js задокументирован здесь


3

Не встроен в сам Javascript. Некоторые из распространенных структур Javascript (например, Prototype) предоставляют реализацию, или вы можете написать свою собственную.


1
Ссылка на сайт? Кроме того, мне было бы более интересно, если бы jQuery сделал это, так как это то, что я использую ...
pq.


2
Однако обратите внимание, что jQuery один ведет себя немного иначе, чем Python, в том смысле, что он возвращает объект, а не массив ... и, таким образом, не может сжать более 2 списков вместе.
Янтарь

Правильно, автор не должен называть jQuery один эквивалентом.
стр.

3

Как @Brandon, я рекомендую Underscore «сек зип функцию. Тем не менее, он действует как zip_longestдобавление undefinedзначений по мере необходимости, чтобы возвращать что-то длиной самого длинного ввода.

Я использовал mixinметод для расширения подчеркивания с помощью a zipShortest, который действует как Python zip, основанный на собственном источнике библиотекиzip .

Вы можете добавить следующее к вашему общему JS-коду и затем вызвать его, как если бы оно было частью подчеркивания: например, _.zipShortest([1,2,3], ['a'])возвращает [[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;
}});

Downvote без комментариев? Я рад улучшить этот ответ, но не могу без обратной связи.
Пэт

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

И это классическая идиома питона "n-group" zip(*[iter(a)]*n):

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

Интересно, что не так с этим, я написал точно такой же. Для меня это намного лучше, чем для всех остальных, но, может быть, мы оба не правы ... Я искал способ добавить типы потоков, но я изо всех сил: D.
cglacet

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! Кнопка Run не показала ваш файл console.log на панели результатов. Зачем?
шт.

Для этого (console.log) требуется нечто вроде Firebug. Просто переключитесь console.logна alert.

Для чего нужна панель Result?
шт.

Это показывает 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])))
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.