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 on Enumerable за пару років! Будь-яка "функціональна" бібліотека, як підкреслення / подача (лодаш 3.x має ліниву оцінку послідовності), забезпечить еквівалентну функцію zip.
користувач2864740

@ user2864740 Інтерпретований цикл (наприклад, у Python) завжди буде набагато повільніше, ніж цикл машинного коду. Цикл, складений JIT (наприклад, у сучасних двигунах JS), може наближатися до назви швидкості процесора, настільки, що посилення, введене за допомогою циклу машинного коду, може бути компенсовано накладними витратами на анонімний виклик функції. Тим не менш, має сенс мати ці вбудовані функції та профілювати кілька варіантів вашої "внутрішньої петлі" з кількома двигунами JS. Результати можуть бути не очевидними.
Тобія

Відповіді:


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

Вищесказане передбачає, що масиви мають однаковий розмір, як і повинні бути. Він також передбачає, що ви передаєте один аргумент списку списків, на відміну від версії Python, де список аргументів є варіативним. Якщо ви хочете всі ці "функції", дивіться нижче. Це займає близько двох додаткових рядків коду.

Далі буде імітувати 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 aka. Версія з кількома аргументами), 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 не має методу" map "", ймовірно, це випадок спроби використовувати це на об'єкті, який не має методу map (наприклад, ноделіста або рядка), який був висвітлений у додатку до цієї публікації
ninjagecko

Незважаючи на те, що варіатична версія ES6 не зберігає zip(zip(x)) = x, ви все ще можете упевнитися в цьому zip(...zip(...x)) = x.
Метт Крамер

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

34

Перевірте бібліотеку підкреслення .

Підкреслення надає понад 100 функцій, які підтримують як улюблені функціональні помічники робочих днів: карта, фільтр, виклик - а також більш спеціалізовані товари: прив’язка функцій, шаблони javascript, створення швидких індексів, глибоке тестування рівності тощо.

- Скажіть люди, які це зробили

Нещодавно я почав використовувати його спеціально для zip()функції, і це залишило чудове перше враження. Я використовую jQuery та CoffeeScript, і це просто ідеально з ними. Підкреслення підбирається саме там, де вони залишаються, і поки що це мене не підвело. О, до речі, це лише 3 кб.

Перевір:

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

Пов'язана ефективність:

Використання mapover for-loops:

Див.: Який найефективніший спосіб об'єднання [1,2] та [7,8] у [[1,7], [2,8]]

zip-тести

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


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

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 ()) у масиві, і ми перериваємось, якщо це так.
Димитріс

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_lo most. Реалізація на JS / ES6 виглядає так:

Реалізація ZIP-програми Python на 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, помилково, 212], [3, -378, 323], ['а', '337', 433]]

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

[]

Реалізація zip_lo most Python на JS / ES6

( https://docs.python.org/3.5/library/itertools.html?highlight=zip_logest#itertools.zip_lo most )

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, невизначений], [2, помилковий, невизначений, невизначений],
[3, -378, невизначений, невизначений], ['а', '337', невизначений, невизначений]]

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 ',' is None ',' is None ']]


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, який можна використовувати як версію JavaScript в python zip:

zip-масив - еквівалент javascript функції zip Python. Об'єднує значення кожного з масивів.

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

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

Інший альтернативний вибір - для користувачів Tensorflow.js: якщо вам потрібна zipфункція python для роботи з наборами даних tensorflow в Javascript, ви можете використовувати tf.data.zip()в Tensorflow.js.

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


3

Не вбудований у сам Javascript. Деякі загальні рамки Javascript (наприклад, прототип) забезпечують реалізацію, або ви можете написати власну.


1
Посилання? Крім того, мені було б більше цікаво, якби це зробив jQuery, оскільки саме це я використовую ...
pq.


2
Однак зауважте, що jQuery поводиться дещо інакше, ніж Python, тому що він повертає об'єкт, а не масив ... і, таким чином, не може збирати більше двох списків разом.
Бурштин

Правильно, автор не повинен називати jQuery одним еквівалентом.
pq.

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-групи" пітона 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! Кнопка "Запустити" не показала ваш результат console.log на панелі "Результати". Чому?
pq.

Він (console.log) вимагає запускати щось на зразок Firebug. Просто перейдіть console.logна alert.

Яка тоді область результатів?
pq.

Він показує HTML скрипки. У цьому випадку я просто роблю прямий JS. Ось результат за допомогою document.write() jsfiddle.net/PyTWw/5

-1

Це голить рядок від відповіді на основі ітератора Дді :

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.