2015-05-08 9 views
8

Quando map ha ingressi differente lunghezza, un valore di riempimento di None viene utilizzata per gli ingressi mancanti:Perché la mappa funziona come izip_longest con fill = None?

>>> x = [[1,2,3,4],[5,6]] 
>>> map(lambda *x:x, *x) 
[(1, 5), (2, 6), (3, None), (4, None)] 

Questo è lo stesso comportamento:

>>> import itertools 
>>> list(itertools.izip_longest(*x)) 
[(1, 5), (2, 6), (3, None), (4, None)] 

Qual è la ragione map fornisce questo comportamento e non il seguente?

>>> map(lambda *x:x, *x) 
[(1, 5), (2, 6), (3,), (4,)] 

... e c'è un modo semplice per ottenere quest'ultimo comportamento sia con un certo sapore di zip o map?

+1

E ' è ben [documentato] (https://docs.python.org/2.7/library/functions.html#map), ed è stato rimosso (o corretto) in Python 3 per renderlo equivalente a 'itertools.imap'. La ragione può essere da qualche parte nel vecchio codice sorgente CPYthon o nella mailing list. –

+4

Considera: quale dovrebbe essere il risultato su '[[1,2,3,4], [5,6], [7,8,9,10]]' e sarebbe diverso dal risultato su '[[ 1,2,3,4], [5,6,7,8], [9,10]] '? – nneonneo

+1

* Qual è la ragione per cui la mappa fornisce questo comportamento, e non il seguente? * Se una funzione richiede due argomenti posizionali richiesti, si otterrà un errore se ne passi uno solo. Quindi IMO, è più sensato che None sostituisca un argomento mancante. Inoltre controlla il commento di @Nneonneo. Il riempimento in Nessuno è l'unico modo per assicurare che gli elementi di uno e uno solo iterabile siano mappati su ciascun argomento posizionale. – Shashank

risposta

5

Penso che questo una decisione di progettazione che gli sviluppatori di base optato per al momento implementato map. Non c'è alcun comportamento universalmente definito per map quando viene utilizzato con più iterables, citando Map (higher-order function):

Mappa con 2 o più liste incontra la questione del trattamento quando le liste sono di diverse lunghezze. Diverse lingue differiscono su questo; alcuni sollevano un'eccezione, alcuni si fermano dopo la lunghezza dell'elenco più breve e ignorano gli elementi extra negli altri elenchi; alcuni continuano sulla lunghezza dell'elenco più lungo e per gli elenchi che sono già terminati, passa un valore segnaposto alla funzione che non indica alcun valore.

Quindi, gli sviluppatori di base Python optato per None come segnaposto per iterables più brevi, al momento map was introduced a Python nel 1993.

Ma in caso di itertools.imap entra in corto circuito con il più breve iterabile perché il suo design è fortemente ispirato da linguaggi come Standard ML, Haskell e APL. In Standard ML e Haskell map termina con l'iterabile più breve (non sono sicuro dell'APL).

Python 3 rimosso anche il map(None, ...) (o dovremmo dire itertools.imap, Python 3 di map è in realtà quasi itertools.imap: Move map() from itertools to builtins) costruire perché era presente in Python 2 solo perché al momento map stato aggiunto a Python c'era alcuna funzione zip() in Python.

Da Issue2186: map and filter shouldn't support None as first argument (in Py3k only):

Sono d'accordo con Guido che non avremmo mai creato map(None, ...) se zip() fosse esistito. Il caso d'uso principale è obsoleto.


Per ottenere il risultato desiderato Io suggerirei di usare itertools.izip_longest con un valore sentinel (object()), piuttosto che di default None, None si romperà le cose se le iterabili si contengono None:

from itertools import izip_longest 


def solve(seq): 
    sentinel = object() 
    return [tuple(x for x in item if x is not sentinel) for item in 
      izip_longest(*seq, fillvalue=sentinel)] 


print solve([[1,2,3,4],[5,6]]) 
# [(1, 5), (2, 6), (3,), (4,)] 
+0

Sembra buono. Prenderò che tu stia dicendo che non esiste un modo semplice per eliminare il comportamento troncato da alcuni elementi di 'zip' o' map' ... –

+2

Oh, hai trovato riferimenti più appropriati +1 :) Ho rinunciato dopo un po 'di tempo e [ ha chiesto a Tim] (http://stackoverflow.com/questions/20320585/python-what-are-lambdas-for/20320604#comment48368640_20320604) se potesse aiutare qui: D – thefourtheye

+0

'sentinel = object()': buon suggerimento, grazie . –

1

Dato che il primo elenco è sempre più lungo e che ci sono solo due liste, si potrebbe fare qualcosa di simile:

x = [1,2,3,4,5] 
y = ['a','b'] 
zip(x,y) + [(i,) for i in x[len(y):]] 
[(1, 'a'), (2, 'b'), (3,), (4,), (5,)] 
+0

Sì, e il passaggio finale sarebbe utilizzare 'itertools.starmap':' lista (starmap (lambda * x: x, that_list)) ' – Shashank

+0

Questo aggiunge un elenco a' zip', quindi no Inoltre, puoi farlo: 'map (lambda * x: tupla (i for i in x se non è None), * x)'. Quindi, non è quello che stavo cercando. –