JavaScript odpowiednik funkcji zip Pythona

216

Czy istnieje javascript równoważny z funkcją zip Pythona? Oznacza to, że biorąc pod uwagę wiele tablic o równej długości, należy utworzyć tablicę par.

Na przykład, jeśli mam trzy tablice, które wyglądają tak:

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

Tablica wyjściowa powinna być:

var output array:[[1,'a',4], [2,'b',5], [3,'c',6]]
pq.
źródło
5
Czy można powiedzieć, że my, programiści Python, „boimy się” głupich metod obejmujących pętle, ponieważ są one powolne i dlatego zawsze szukają wbudowanych metod wykonywania zadań. Ale że w Javascript powinniśmy się z tym pogodzić i napisać nasze pętle, ponieważ nie są one szczególnie wolne?
LondonRob
3
@LondonRob Pętla to pętla, ukryta za „szybką” metodą lub nie. JavaScript ma na pewno było uzyskanie większego wsparcia dla funkcja wyższego rzędu, z wprowadzeniem Array forEach, reduce, map, every, itd. To było po prostu zdarza się, że zipnie „dokonać cięcia” (a flatMaptakże jej brak), nie ze względów wydajności - ale szczerze mówiąc, .NET (3.5) nie miał Zip'a na Enumerable od kilku lat! Każda biblioteka „funkcjonalna”, taka jak podkreślenie / lodash (lodash 3.x ma ocenę leniwej sekwencji), zapewni równoważną funkcję zip.
user2864740,
@ user2864740 Pętla interpretowana (taka jak w Pythonie) zawsze będzie znacznie wolniejsza niż pętla kodu maszynowego. Pętla skompilowana w JIT (na przykład we współczesnych silnikach JS) może zbliżyć się do natywnej prędkości procesora, tak że wzmocnienie wprowadzone za pomocą pętli kodu maszynowego może zostać zrównoważone przez obciążenie anonimowego wywołania funkcji. Mimo to sensowne jest posiadanie tych wbudowanych funkcji i profilowanie kilku odmian „wewnętrznych pętli” za pomocą kilku silników JS. Wyniki mogą nie być oczywiste.
Tobia,

Odpowiedzi:

178

Aktualizacja 2016:

Oto wersja Ecnacript 6 Snazziera:

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

Ilustracja ekwiwal. do Pythona { zip(*args)}:

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

(a FizzyTea wskazuje, że ES6 ma składnię argumentów variadic, więc poniższa definicja funkcji będzie działać jak python, ale zobacz poniżej zrzeczenie się odpowiedzialności ... nie będzie to jej własna odwrotność, więc zip(zip(x))nie będzie równa x; chociaż, jak zauważa Matt Kramer zip(...zip(...x))==x(jak w zwykłym pythoniezip(*zip(*x))==x ))

Alternatywna definicja ekwiwal. do Pythona { 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

(Należy pamiętać, że ... składni mogą występować problemy z wydajnością w tym czasie i być może w przyszłości, więc jeśli użyjesz drugiej odpowiedzi z argumentami variadic, możesz ją dokładnie przetestować).


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

Powyższe zakłada, że ​​tablice są równej wielkości, tak jak powinny. Zakłada się także, że przekazujesz argument z jednej listy list, w przeciwieństwie do wersji Pythona, w której lista argumentów jest variadic.Jeśli chcesz wszystkie te „funkcje”, patrz poniżej. Zajmuje to około 2 dodatkowe linie kodu.

Poniższe będzie naśladować zipzachowanie Pythona w przypadkach krawędziowych, w których tablice nie są równej wielkości, w milczeniu udając, że dłuższe części tablic nie istnieją:

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

To naśladuje itertools.zip_longestzachowanie Pythona , wstawiając undefinedtam , gdzie tablice nie są zdefiniowane:

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

Jeśli użyjesz tych dwóch ostatnich wersji (variadic aka. Wersje z wieloma argumentami), zip nie będzie już własną odwrotnością. Aby naśladować zip(*[...])idiom z Pythona, musisz to zrobić, zip.apply(this, [...])gdy chcesz odwrócić funkcję zip lub jeśli podobnie chcesz mieć zmienną liczbę list jako dane wejściowe.


uzupełnienie :

Aby uczynić ten uchwyt dowolną iterowalną (np. W Pythonie możesz używać zipna ciągach, zakresach, obiektach mapy itp.), Możesz zdefiniować:

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

Jeśli jednak napiszesz zipw następujący sposób , nawet to nie będzie konieczne:

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

Próbny:

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

(Lub możesz użyć range(...)funkcji w stylu Pythona, jeśli już ją napisałeś. W końcu będziesz mógł używać wyrażeń tablicowych ECMAScript lub generatorów.)

ninjagecko
źródło
1
To nie działa dla mnie: TypeError: Object 1 nie ma metody 'map'
Emanuele Paolini
7
I ES6 dla argumentów variadic i wszelkich iterowalnych:zip = (...rows) => [...rows[0]].map((_,c) => rows.map(row => row[c]));
1983
„Obiekt 1 nie ma metody 'map” ”to prawdopodobnie próba użycia tego na obiekcie, który nie ma metody mapy (takiej jak lista węzłów lub ciąg znaków), która została opisana w dodatku do tego postu
ninjagecko
Chociaż prawdą jest, że wariant ES6 nie zachowuje zip(zip(x)) = x, nadal możesz się w tym cieszyć zip(...zip(...x)) = x.
Matt Kramer,
const the_longest_array_length = Math.max(...(arrays.map(array => array.length)));
Константин Ван
34

Sprawdź bibliotekę Underscore .

Underscore zapewnia ponad 100 funkcji, które wspierają zarówno twoich ulubionych pomocników funkcjonalnych w dniu pracy: mapowanie, filtrowanie, wywoływanie - a także bardziej wyspecjalizowane gadżety: wiązanie funkcji, tworzenie szablonów javascript, tworzenie szybkich indeksów, testowanie głębokiej równości i tak dalej.

- Powiedz ludziom, którzy to zrobili

Niedawno zacząłem używać go specjalnie do tej zip()funkcji i pozostawiło to pierwsze wrażenie. Używam jQuery i CoffeeScript, i to doskonale do nich pasuje. Podkreślenie zaczyna się tam, gdzie kończy i do tej pory mnie nie zawiodło. A tak przy okazji, to tylko 3kb zminimalizowane.

Sprawdź to:

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
// returns [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
Brandon
źródło
3
Korzystając z podkreślenia, czujesz się nieco bliżej jasności i logicznej przytulności Haskella.
CamilB
12
Zamiast podkreślenia, wypróbuj to: lodash.com - zamiennik, ten sam świetny smak, więcej funkcji, więcej spójności między przeglądarkami, lepsza perf. Zobacz kitcambridge.be/blog/say-hello-to-lo-dash dla opisu.
Merlyn Morgan-Graham
16

Oprócz doskonałej i kompleksowej odpowiedzi ninjagecko, wystarczy spakować dwie tablice JS w „mimikę krotkową”:

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

Objaśnienie:
Ponieważ JavaScript nie ma tuplestypu, funkcje krotek, list i zestawów nie miały wysokiego priorytetu w specyfikacji języka.
W przeciwnym razie podobne zachowanie jest dostępne w prosty sposób poprzez mapę macierzy w JS> 1.6 . ( mapjest faktycznie często wdrażany przez producentów silników JS w wielu silnikach> JS 1.4, mimo że nie jest określony).
Główną różnicą do Pythona zip, izip... wynika z map„s stylu funkcjonalnym, ponieważ mapwymaga function-argument. Dodatkowo jest to funkcja Array-instance. Array.prototype.mapZamiast tego można użyć , jeśli dodatkowa deklaracja dla danych wejściowych stanowi problem.

Przykład:

_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]]})

Wynik:

//'('+_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)

Powiązana wydajność:

Korzystanie mapz forpętli:

Zobacz: Jaki jest najbardziej efektywny sposób łączenia [1,2] i [7,8] w [[1,7], [2,8]]

testy zip

Uwaga: typy podstawowe, takie jak falsei undefinednie posiadają prototypowej hierarchii obiektów, a zatem nie ujawniają toStringfunkcji. Dlatego są wyświetlane jako puste na wyjściu.
Ponieważ parseIntdrugim argumentem jest podstawa podstawa / liczba, na którą należy przekonwertować liczbę, a ponieważ mapindeks jest przekazywany jako drugi argument do funkcji argumentu, używana jest funkcja otoki.

Lorenz Lo Sauer
źródło
Twój pierwszy przykład mówi „aIn nie jest funkcją”, kiedy próbuję. Działa, jeśli wywołam .map z tablicy zamiast jako prototyp: aIn.map(function(e, i) {return [e, aOut[i]];})Co jest nie tak?
Noumenon,
1
@Noumenon, Array.prototype.mappowinno być Array.prototype.map.call, naprawiłem odpowiedź.
użytkownik
11

Nowoczesny przykład ES6 z generatorem:

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

Po pierwsze, otrzymujemy listę iteracji jako iterators. Zwykle dzieje się to w sposób przejrzysty, ale tutaj robimy to wyraźnie, ponieważ wykonujemy krok po kroku, aż jeden z nich się wyczerpie. Sprawdzamy, czy którykolwiek z wyników (przy użyciu .some()metody) w danej tablicy jest wyczerpany, a jeśli tak, przerywamy pętlę while.

Dimitris
źródło
Ta odpowiedź przydałaby się więcej wyjaśnień.
cmaher
1
Otrzymujemy listę iteratorów z iteracji. Zwykle dzieje się to w sposób przejrzysty, tutaj robimy to wyraźnie, ponieważ poddajemy się krok po kroku, aż jeden z nich się wyczerpie. Sprawdź, czy któraś z nich (metoda .some ()) w tablicy jest wyczerpana, a jeśli tak, to przerywamy.
Dimitris,
11

Wraz z innymi funkcjami podobnymi do Pythona, pythonicoferuje zipfunkcję, z tą dodatkową korzyścią, że zwraca leniwy oceniany Iterator, podobny do zachowania jego odpowiednika w Pythonie :

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

Ujawnienie Jestem autorem i opiekunem Pythonic

Keyvan
źródło
7

Python ma dwie funkcje: zip i itertools.zip_longest. Implementacja w JS / ES6 wygląda następująco:

Implementacja zip Pythona na 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])));
};

Wyniki:

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],
    []
));

[]

Implementacja zip_longest Pythona na 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
        )
    );
};

Wyniki:

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

[[1, 667, 111, niezdefiniowany], [2, fałszywy, niezdefiniowany, niezdefiniowany],
[3, -378, niezdefiniowany, niezdefiniowany], [„a”, „337”, niezdefiniowany, niezdefiniowany]]

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, „Is None”], [2, false, „Is None”, „Is None”],
[3, -378, „Is None”, „Is None”], [„a ”,„ 337 ”,„ Is None ”,„ Is None ”]]

PADYMKO
źródło
4

Możesz ustawić funkcję narzędzia za pomocą 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]]

Jednak w powyższym rozwiązaniu długość pierwszej tablicy określa długość tablicy wyjściowej.

Oto rozwiązanie, w którym masz nad nim większą kontrolę. Jest to trochę skomplikowane, ale warto.

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

Bhargav Patel
źródło
4

1. Moduł Npm: zip-array

Znalazłem moduł npm, który można wykorzystać jako javascriptową wersję Pythona zip:

zip-array - javascript równoważny z funkcją zip Pythona. Scala wartości każdej z tablic.

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

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

Inną alternatywną opcją jest dla użytkowników Tensorflow.js: jeśli potrzebujesz zipfunkcji w Pythonie do pracy z zestawami danych tensorflow w JavaScript, możesz użyć tf.data.zip()Tensorflow.js.

Dokumentacja tf.data.zip () w Tensorflow.js tutaj

Huan
źródło
3

Nie wbudowany w sam Javascript. Niektóre popularne frameworki JavaScript (takie jak Prototype) zapewniają implementację lub możesz napisać własną.

Bursztyn
źródło
1
Połączyć? Byłbym również bardziej zainteresowany, gdyby to zrobił jQuery, ponieważ właśnie tego używam ...
pq.
2
Zauważ jednak, że jQuery zachowuje się nieco inaczej niż Python, ponieważ zwraca obiekt, a nie tablicę ... i dlatego nie może skompresować więcej niż 2 list razem.
Amber
Racja, autor nie powinien nazywać jQuery równorzędnym.
pq.
3

Jak @Brandon, polecam podkreślenia „s zip funkcji. Działa jednak tak zip_longest, jakby undefinedw razie potrzeby dodawano wartości, aby zwrócić coś o długości najdłuższego wejścia.

Użyłem tej mixinmetody do rozszerzenia podkreślenia o zipShortest, który działa jak Python zip, w oparciu o własne źródło bibliotekizip .

Możesz dodać następujące elementy do wspólnego kodu JS, a następnie wywołać go tak, jakby był częścią podkreślenia: _.zipShortest([1,2,3], ['a']) zwraca [[1, 'a']], na przykład.

// 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;
}});
Poklepać
źródło
Głosuj bez komentarza? Cieszę się, że mogę poprawić tę odpowiedź, ale nie mogę tego zrobić bez opinii.
Pat
2

Możesz zmniejszyć tablicę tablic i odwzorować nową tablicę, biorąc wynik indeksu tablicy wewnętrznej.

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

Nina Scholz
źródło
1

Odmiana leniwego generatora :

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

Oto klasyczny idiom pytona „n-group” zip(*[iter(a)]*n):

triples = [...zip(...Array(3).fill(iter(a)))]
Georg
źródło
Zastanawiam się, co jest nie tak z tym, napisałem dokładnie ten sam. Dla mnie wydaje się to o wiele lepsze niż wszystkie inne, ale może oboje się mylimy ... Szukałem sposobu, aby dodać do niego typy przepływu, ale walczę: D.
cglacet
0

Mochikit biblioteka zapewnia to i wiele innych funkcji takich jak Python. twórca Mochikit jest także fanem Pythona, więc ma ogólny styl Pythona, a także otacza wywołania asynchroniczne w pokręconej strukturze.

Keith
źródło
0

Pobiegłem do tego w czystym JS, zastanawiając się, w jaki sposób wtyczki opublikowane powyżej wykonały zadanie. Oto mój wynik. Przedmówię to mówiąc, że nie mam pojęcia, jak stabilne będzie to w IE i tym podobnych. To tylko szybka makieta.

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

Nie jest kuloodporny, ale nadal jest interesujący.

Pan Polywhirl
źródło
jsfiddle wygląda ładnie. Ma przycisk TidyUp! Przycisk Uruchom nie wyświetlał danych wyjściowych pliku console.log w panelu wyników. Czemu?
pq.
To (console.log) wymaga czegoś takiego jak Firebug. Po prostu przełącz się console.logna alert.
Do czego służy okienko wyników?
pq.
Pokazuje HTML skrzypiec. W tym przypadku po prostu robię proste JS. Oto wynik przy użyciu document.write() jsfiddle.net/PyTWw/5
-1

To odcina linię od odpowiedzi opartej na iteratorze 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);
  }
}
Steven Kalt
źródło
-1

Jeśli nie masz nic przeciwko ES6:

const zip = (arr,...arrs) =>(
                            arr.map(
                              (v,i) => arrs.reduce((a,arr)=>[...a, arr[i]], [v])))
T.Chmelevskij
źródło