Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eliminating redundant function calls in comprehensions from within the comprehension

Say we need a program which takes a list of strings and splits them, and appends the first two words, in a tuple, to a list and returns that list; in other words, a program which gives you the first two words of each string.

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

It can be written like so (we assume valid input):

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

But a list comprehension would be much nicer.

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

But this involves two calls to split(). Is there a way to perform the split only once from within the comprehension? I tried what came naturally and it was invalid 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
like image 239
2rs2ts Avatar asked Jul 10 '13 01:07

2rs2ts


3 Answers

Well, in this particular case:

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

Otherwise, though, you can use one generator expression:

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

And if performance is actually critical, just use a function.

like image 167
Ry- Avatar answered Nov 20 '22 10:11

Ry-


Writing what comes to mind naturally from English and hoping it's valid syntax rarely works, unfortunately.

The generalized form of what you're trying to do is bind some expression to a name within a comprehension. There's no direct support to that, but since a for clause in a comprehension binds a name to each element from a sequence in turn, you can use for over single-element containers to achieve the same effect:

>>> 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')]
like image 40
Ben Avatar answered Nov 20 '22 10:11

Ben


I think using a genexp is nicer, but here's how to do it with a lambda. There may be cases when this is a better fit

>>> [(lambda splt:(splt[0], splt[1]))(s.split()) for s in input]
[('hello', 'world'), ('foo', 'bar')]
like image 37
John La Rooy Avatar answered Nov 20 '22 08:11

John La Rooy