2013-07-10 1 views
5

Sagen wir brauchen ein Programm, das eine Liste von Zeichenfolgen und spaltet sie, und hängt die ersten beiden Wörter, in einem Tupel, an eine Liste und gibt diese Liste zurück; Mit anderen Worten, ein Programm, das Ihnen die ersten zwei Wörter jeder Zeichenfolge gibt.Vermeiden redundante Funktionsaufrufe in Comprehensions aus dem Verständnis

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

Es kann wie so geschrieben werden (wir gültige Eingabe übernehmen):

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

Aber eine Liste Verständnis wäre viel schöner.

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

Aber dies beinhaltet zwei Aufrufe an split(). Gibt es eine Möglichkeit, die Aufteilung nur einmal innerhalb des Verständnisses durchzuführen? Ich habe versucht, was kam, natürlich, und es war eine ungültige Syntax:

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

Wenn Sie verwenden 'with' wollen, sollte das Objekt erhalten Methoden' __enter__' und '__exit__'. Liste kann hier nicht verwendet werden. – zhangyangyu

+0

'Eingang' ist ein eingebauter, wahrscheinlich nicht die beste Idee, um es als Variable zu verwenden –

+0

@gnibbler Es war keine Variable - ich benutzte den Code-Block nur, um es zu formatieren. Ich sollte den Operator '=' aber wahrscheinlich nicht verwenden. – 2rs2ts

Antwort

6

Nun, in diesem speziellen Fall:

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

Ansonsten aber können Sie einen Generator Ausdruck verwenden :

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

Und wenn Leistung tatsächlich kritisch ist, verwenden Sie einfach eine Funktion.

+2

+1 Der Generator Ausdruck scheint zu sein, was die Frage über –

+0

Ich bin ziemlich sicher, das ist, was ich gesucht habe - schade, dass ich nicht für weitere 5 Minuten akzeptieren kann. Ich denke, das gibt mir Zeit, es zu testen. Ich wette, dass das Nesting dieser Generatoren Spaß machen wird! :) – 2rs2ts

+1

+1 Es könnte besser sein, '.split (None, 2)' hier anstelle von '.split()' zu verwenden (da wir uns nur für die ersten beiden Wörter interessieren), nur als Nebensatz. – arshajii

1

Wie das?

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

Es verwendet List-Splicing. Es wird eine Liste natürlich zurück, aber wenn Sie ein Tupel möchten, können Sie verwenden:

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

Ich habe nur ein elementares Beispiel für die Frage gegeben. Ich weiß, dass Sie das Slice in diesem Beispiel ausführen können, aber natürlich können Sie das nicht für jeden Fall tun. – 2rs2ts

+0

@ 2rs2ts Von welchen anderen Fällen sprechen Sie? Ich bin mir nicht sicher, ob ich verstehe, was du meinst. –

+1

Ich weiß es nicht, irgendeine alte Sache. Nehmen wir an, Sie haben mit benannten Tupeln zu tun, die von einer Funktion 'foo()' zurückgegeben werden und die 'bar',' baz' haben und viele andere Teile haben können, abhängig vom Aufruf von 'foo()' - but Sie wollen nur 'bar' und' baz' und können sich nicht auf ihre Position in den Tupeln verlassen. – 2rs2ts

4

Schreiben, was natürlich aus dem Englischen in den Sinn kommt und hofft, dass seine gültige Syntax selten funktioniert, leider.

Die verallgemeinerte Form von dem, was Sie versuchen, ist, einen Ausdruck an einen Namen innerhalb eines Verständnisses zu binden. Es gibt keine direkte Unterstützung für das, aber da eine for Klausel in einem Verständnis einen Namen aus einer Folge wiederum zu jedem Elemente bindet, Sie for über Einzelelement-Behälter verwenden, können den gleichen Effekt zu erzielen:

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

Ich brauchte ein paar Durchleuchtungen, um zu verstehen, was Sie mit "bindet einen Namen an jedes Element aus einer Sequenz in "Aber wenn ich verstanden habe, dass es für mich sehr viel Sinn gemacht hat - vielen Dank! – 2rs2ts

2

I denke mit einem Genexp ist schöner, aber hier ist, wie es mit einem lambda zu tun. Es kann Fälle geben, wenn dies ist eine bessere Passform

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

MiniTech Antwort ist der richtige Weg, es zu tun.

Aber beachten Sie, dass Sie nicht alles in einer Zeile tun müssen, und Sie wirklich nichts gewinnen.

Dies:

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

tut genau das Gleiche wie folgt aus:

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

Keine zusätzlichen Zwischenwerte gebaut, keine Auswirkungen auf die Garbage Collection, nur mehr Lesbarkeit kostenlos.

Auch gibt es eine gute Chance, Ihren echten Code tatsächlich keine Liste am Ende braucht, nur etwas iterable, in welchem ​​Fall du bist besser dran mit diesem:

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

Oder in Python 3.3+:

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

In der Tat kann eine ganze Menge von Programmen geschrieben werden auf diese Weise-eine Reihe von Generator Ausdrücke, gefolgt von return ing/yield from ing ein letztes Genex pr/Listenkomp.

+0

Manchmal benötigt mein Code eine Sammlung, manchmal ist ein iterables besser - es kommt darauf an! Es ist schön, Beispiele zu sehen. (Und natürlich weiß ich, dass ich nichts in einer Zeile tun muss. Ich wollte nur sehen, wie ich es erreichen kann.) – 2rs2ts

+0

@ 2rs2ts: Denken Sie daran, dass Sie einen Iterator immer in eine "Liste" verwandeln können "liste" darauf an, fast kostenlos, aber umgekehrt kostet es. (Und der Punkt der "Alles in einer Zeile" -Ding ist, dass, anders als beim Schreiben einer expliziten Funktion oder Schleife, hier der Fluss überhaupt nicht geändert werden muss; Sie können den Ausdruck einfach trivial zu seinem extrahieren eigene Linie.) – abarnert

0

itemgetter kann hier verwendet werden. Es ist ein bisschen allgemeiner als s.split()[:2]. Es ermöglicht Ihnen, beliebige Artikel aus 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')] 

allgemeiner ziehen:

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