2013-07-10 11 views
5

Supponiamo di aver bisogno di un programma che prende un elenco di stringhe e le divide e aggiunge le prime due parole, in una tupla, a un elenco e restituisce tale elenco; in altre parole, un programma che ti dà le prime due parole di ogni stringa.Eliminazione di chiamate di funzioni ridondanti in comprensione dall'interno della comprensione

input: ["hello world how are you", "foo bar baz"] 
output: [("hello", "world"), ("foo", "bar")] 

Può essere scritto in questo modo (si suppone di ingresso valido):

def firstTwoWords(strings): 
    result = [] 
    for s in strings: 
     splt = s.split() 
     result.append((splt[0], splt[1])) 
    return result 

Ma una lista di comprensione sarebbe molto più bello.

def firstTwoWords(strings): 
    return [(s.split()[0], s.split()[1]) for s in strings] 

Ma questo comporta due chiamate a split(). C'è un modo per eseguire la divisione solo una volta all'interno della comprensione? Ho provato quello che è venuto naturale ed è stato sintassi non valida:

>>> [(splt[0],splt[1]) for s in strings with s.split() as splt] 
    File "<stdin>", line 1 
    [(splt[0],splt[1]) for s in strings with s.split() as splt] 
             ^
SyntaxError: invalid syntax 
+0

Se si desidera utilizzare 'with', l'oggetto dovrebbe ottenere metodi' '__enter__' e __exit__'. elenco non può essere utilizzato qui. – zhangyangyu

+0

'input' è un builtin, probabilmente non è la migliore idea per usarlo come variabile –

+0

@gnibbler Non era una variabile - Ho usato il blocco di codice solo per dargli la formattazione. Probabilmente non dovrei usare l'operatore '='. – 2rs2ts

risposta

6

Ebbene, in questo caso particolare:

def firstTwoWords(strings): 
    return [s.split()[:2] for s in strings] 

In caso contrario, però, è possibile utilizzare uno generatore di espressione :

E se le prestazioni sono davvero critiche, basta usare una funzione.

+2

+1 L'espressione del generatore sembra essere quello che la domanda sta ponendo riguardo a –

+0

Sono abbastanza certo che questo è quello che stavo cercando - peccato che non possa accettare per altri 5 minuti. Immagino che questo mi dia il tempo di provarlo. Scommetto che annidare questi generatori sarà divertente! :) – 2rs2ts

+1

+1 Potrebbe essere meglio usare '.split (None, 2)' qui invece di '.split()' (dato che ci interessano solo le prime due parole), proprio come un accantonamento. – arshajii

1

Ti piace?

def firstTwoWords(strings): 
    return [s.split()[:2] for s in strings] 

Utilizza lo splicing di elenchi. Si restituirà un elenco, naturalmente, ma se volete una tupla, è possibile utilizzare:

def firstTwoWords(strings): 
    return [tuple(s.split()[:2]) for s in strings] 
+1

Ho appena dato un esempio elementare per il gusto della domanda. So che potresti eseguire la sezione in questo esempio, ma ovviamente non puoi farlo per tutti i casi. – 2rs2ts

+0

@ 2rs2ts Di quali altri casi parli? Non sono sicuro di aver capito cosa intendi. –

+1

Non lo so, nessuna vecchia cosa. Supponiamo che tu abbia a che fare con tuple denominate restituite da una funzione 'foo()' e che hanno 'bar',' baz' e possono avere molte altre parti, a seconda del risultato della chiamata 'foo()' - ma vuoi solo 'bar' e' baz', e non puoi fare affidamento sulla loro posizione nelle tuple. – 2rs2ts

4

Scrivere ciò che viene in mente naturalmente dall'inglese e sperare che la sua sintassi valida funzioni raramente, sfortunatamente.

La forma generalizzata di ciò che si sta tentando di fare è legare un'espressione a un nome all'interno di una comprensione. Non c'è alcun supporto diretto a questo, ma dal momento che una clausola for in una comprensione si lega un nome a ciascun elemento da una sequenza, a sua volta, è possibile utilizzare for over contenitori a singolo elemento per ottenere lo stesso effetto:

>>> strings = ["hello world how are you", "foo bar baz"] 
>>> [(splt[0],splt[1]) for s in strings for splt in [s.split()]] 
[('hello', 'world'), ('foo', 'bar')] 
+0

Mi ci sono voluti alcuni read-through per capire cosa intendevi per "lega un nome ad ogni elemento da una sequenza in girare, "ma una volta che ho capito, per me è molto sensato - grazie mille! – 2rs2ts

2

I pensare di usare un genexp è più bello, ma ecco come farlo con un lambda. Ci possono essere casi in cui questo è un adattamento migliore

>>> [(lambda splt:(splt[0], splt[1]))(s.split()) for s in input] 
[('hello', 'world'), ('foo', 'bar')] 
2

La risposta di minitech è il modo giusto per farlo.

Ma si noti che non è necessario eseguire tutto in una riga e non si guadagna nulla.

questo:

splits = (s.split() for s in strings) 
return [(s[0], s[1]) for s in splits] 

fa esattamente la stessa cosa come questa:

return [(s[0], s[1]) for s in (s.split() for s in strings)] 

Nessun valori intermedi in più in fase di costruzione, nessun effetto sulla raccolta dei rifiuti, solo più leggibilità gratuitamente.

Inoltre, c'è una buona probabilità che il codice vero e proprio in realtà non ha bisogno di una lista, alla fine, solo qualcosa iterabile, nel qual caso si sta meglio con questo:

splits = (s.split() for s in strings) 
return ((s[0], s[1]) for s in splits) 

Oppure, in Python 3.3+:

splits = (s.split() for s in strings) 
yield from ((s[0], s[1]) for s in splits) 

In realtà, un sacco di programmi può essere scritto in questo modo, una serie di espressioni generatore seguito da return ing/yield from ing un'ultima genex pr/listcomp.

+0

A volte il mio codice ha bisogno di una collezione, a volte un iterable è migliore - dipende! Comunque è bello vedere degli esempi. (E ovviamente, so che non devo * fare * nulla in una riga, volevo solo vedere come ottenerlo.) – 2rs2ts

+0

@ 2rs2ts: Ricorda che puoi sempre trasformare un iteratore in un 'elenco' di chiamando 'list' su di esso, quasi gratis, ma viceversa. (E il punto della cosa "tutto in una riga" è che, a differenza del caso della scrittura di una funzione o di un ciclo espliciti, qui non è necessario cambiare il flusso, basta semplicemente estrarre l'espressione stessa propria linea.) – abarnert

0

itemgetter può essere utilizzato qui. È un po 'più generale di s.split()[:2]. Esso consente di estrarre gli elementi arbitrari di s

>>> from operator import itemgetter 
>>> strings = ["hello world how are you", "foo bar baz"] 
>>> [itemgetter(0, 1)(s.split()) for s in strings] 
[('hello', 'world'), ('foo', 'bar')] 

più in generale:

>>> [itemgetter(1, 2, 0)(s.split()) for s in strings] 
[('world', 'how', 'hello'), ('bar', 'baz', 'foo')] 
Problemi correlati