4

Consider two list comprehensions gamma and delta with nearly redundant code. The difference being the sliced lists alpha and beta, namely

gamma = [alpha[i:i+30] for i in range(0,49980,30)]
delta = [beta[i:i+30] for i in range(0,49980,30)]

Is there a pythonic way to write this as a one liner (say gamma,delta = ... )?

I have a few other pieces of code that are similar in nature, and I'd like to simplify the code's seeming redundancy.

sunspots
  • 1,047
  • 13
  • 29

4 Answers4

8

Although one-line list-comprehensions are really useful, they aren't always the best choice. So here since you're doing the same chunking to both lists, if you wanted to change the chunking, you would have to modify both lines.

Instead, we could use a function that would chunk any given list and then use a one-line assignment to chunk gamma and delta.

def chunk(l):
    return [l[i:i+30] for i in range(0, len(l), 30)]

gamma, delta = chunk(gamma), chunk(delta)
Joe Iddon
  • 20,101
  • 7
  • 33
  • 54
4

As far as your question related to combining both the list comprehension expression above is concerned, you can get gamma and delta by using zip with single list comprehension as:

gamma, delta = zip(*[(alpha[i:i+30], beta[i:i+30]) for i in range(0,50000,30)])

Sample example to show how zip works:

>>> zip(*[(i, i+1) for i in range(0, 10, 2)])
[(0, 2, 4, 6, 8), (1, 3, 5, 7, 9)]

Here our list comprehension will return the list of tuples:

>>> [(i, i+1) for i in range(0, 10, 2)]
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Then we are unpacking this list using * and using zip we are aggregating the element from each of the iterables:

>>> zip(*[(i, i+1) for i in range(0, 10, 2)])
[(0, 2, 4, 6, 8), (1, 3, 5, 7, 9)]

As an alternative, for dividing the list into evenly sized chunks, please take a look at "How do you split a list into evenly sized chunks?"

Moinuddin Quadri
  • 46,825
  • 13
  • 96
  • 126
1

Just another way...

gamma, delta = ([src[i:i+30] for i in range(0,49980,30)] for src in (alpha, beta))

It's a bit faster than the accepted zip solution:

genny 3.439506340350704
zippy 4.3039169818228515

Code:

from timeit import timeit
alpha = list(range(60000))
beta = list(range(60000))
def genny():
    gamma, delta = ([src[i:i+30] for i in range(0,49980,30)] for src in (alpha, beta))
def zippy():
    gamma, delta = zip(*[(alpha[i:i+30], beta[i:i+30]) for i in range(0,50000,30)])
n = 1000
print('genny', timeit(genny, number=n))
print('zippy', timeit(zippy, number=n))
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
0

You can you lambda expression:

g = lambda l: [l[i:i+30] for i in range(0,50000, 30)]
gamma, delta = g(alpha), g(beta)
Noa
  • 145
  • 1
  • 9
  • 3
    If you're naming a lambda, then there's no point to it being a lambda. – cs95 Dec 31 '17 at 09:13
  • 2
    Please do _not_ use `lambda` for named functions. `lambda` is supposed to be for anonymous functions. If you want a named function, use the `def` syntax. However, I do agree that it's probably a good idea to use a function for this task. – PM 2Ring Dec 31 '17 at 09:14
  • I agree with the comments and I also think that using a function is a better idea for this. It is difficult sometimes to say which is a more pythonic solution. But this seems to be a legit use case for function. – SRC Dec 31 '17 at 09:20
  • I've seen in lots of places that this a legit way to define a one-liner function... why is this wrong? – Noa Dec 31 '17 at 09:21
  • It's not technically wrong, it's just bad style. It only saves a couple of bytes of typing, so what's the benefit of using a `lambda` instead of a proper named function? – PM 2Ring Dec 31 '17 at 09:25
  • @PM2Ring Gotcha – Noa Dec 31 '17 at 09:26
  • 1
    All functions have a `__name__` attribute. This can be handy when debugging, among other situations. (So if you print a function, or convert it to a string, the name attribute is part of that string representation). But a lambda's name is set to `''`, which isn't very informative. ;) – PM 2Ring Dec 31 '17 at 09:30