0

This is a broader case of the classic question of how to select every n-th element in a list ( my_list[::n])

Suppose I have a list l = range(20) and for each batch of n elements, I need to select the i-th to the k-th

For example if for every n=10 elements I need to select the 3rd to the 5th, the result is l2 = [2, 3, 4, 12, 13, 14]

What is a pythonic / elegant way to achieve the same result as a plain loop as shown below?

l = range(20)

n = 10
i = 2 # 2 being the index of the 3rd element
k = 4 # 4 being the index of the 5th element

l2 = []

for i, x in enumerate(l):
    _i = i % n
    if _i >= i and _i <= k:
        l2.append(x)

which results in the desired l2

Pythonic
  • 2,091
  • 3
  • 21
  • 34
  • Instead of s and e, do you mean i and k? Also is the length of the list divisible by n? – Mike Nov 26 '19 at 08:24
  • 2
    IMHO if you are dealing with indices you want to just iterate over indices. Use `range(0, len(seq), n)` to get the start indices of the chunks, then use `seq[j:j+n]` to get the chunk and then `[i:k+1]` to get elements between index `i` and `k` inside that chunk. Use `itertools.chain` to concatenate the results: `chain.from_iterable(seq[j:j+n][i:k+1] for j in range(0, len(seq), n))` – Giacomo Alzetta Nov 26 '19 at 08:28
  • Thanks Mike, amended i/k and the list should not be assumed to be divisible by `n` – Pythonic Nov 26 '19 at 08:29
  • @Pythonic then, is k greater than list_length % n ? – Mike Nov 26 '19 at 08:35
  • yes it is always true that `k >= len(l) % n` - edit: no this is not true apols, you can have `k` be greater or smaller than `len(l) % n` – Pythonic Nov 26 '19 at 10:12

4 Answers4

1

Taking advantage of list splicing, wouldn't it be something like

list1 = range(20)
len_list1 = len(list1)
list2 = []

i = 2
k = 4

n = 10

for chunk in range(0, len_list1, n):
  list2 += list1[chunk : chunk + n][i : k + 1]

print(list2)

Edit: So, I was wondering if the chain.from_iterable is significantly faster so I tried the following benchmark using timeit on repl.it.

Here's the setup:

import timeit

setup = """
from itertools import chain
l = range(100000)
len_l = len(l)
l2 = []

i = 2
k = 4

n = 10
"""

code1 = """
for chunk in range(0, len_l, 10):
  l2 += l[chunk : chunk + n][i:k+1]
"""

code2 = """
list(chain.from_iterable([ l[_i:_i + n][i:k+1] for _i in range(0, len(l), n)]))
"""

print(timeit.timeit(code1, setup=setup, number = 1000))
print(timeit.timeit(code2, setup=setup, number = 1000))

I'm getting

16.52823367900055
17.319514279999566

So, it looks like the itertools approach is ~16x faster for this particular set of inputs.

Mike
  • 627
  • 1
  • 7
  • 21
1

I see that Giacomo commented this as I was making the answer, but here is one solutoin using chain:

from itertools import chain

l = range(20)

n = 10
s = 2 # 2 being the index of the 3rd element
e = 4 # 4 being the index of the 5th element


list(chain.from_iterable([ l[i:i + n][s:e+1] for i in range(0, len(l), n)]))
Christian Sloper
  • 7,440
  • 3
  • 15
  • 28
1

Try this :

>>> from itertools import chain
>>> i = 2
>>> k = 5
>>> n = 10
>>> l = list(range(20))
>>> list(chain.from_iterable([l[x+i:x+k] for x in range(0,len(l),10)]))
[2, 3, 4, 12, 13, 14]
abhilb
  • 5,639
  • 2
  • 20
  • 26
0

Answering my own question as I found a meaningful speedup (only works with sorted arrays)

new_l = []
for index in range(i,k+1):
    new_l += l[i::n]
sorted(new_l)

Expanding Mike's example, code3 is 4x faster than code1 or code2:

import timeit

setup = """
from itertools import chain
l = range(100000)
len_l = len(l)
l2 = []

i = 2
k = 4

n = 10
"""

code1 = """
for chunk in range(0, len_l, 10):
  l2 += l[chunk : chunk + n][i:k+1]
"""

code2 = """
list(chain.from_iterable([ l[_i:_i + n][i:k+1] for _i in range(0, len(l), n)]))
"""

code3 = """
new_l = []
for index in range(i,k+1):
    new_l += l[index::n]
sorted(new_l)
"""

print('code1', timeit.timeit(code1, setup=setup, number = 1000))
print('code2', timeit.timeit(code2, setup=setup, number = 1000))
print('code3', timeit.timeit(code3, setup=setup, number = 1000))

which results in

code1 10.907166325010826
code2 9.769439743633773
code3 2.1961751914831957
Pythonic
  • 2,091
  • 3
  • 21
  • 34