8

Given a list:

import string
a = list(string.ascii_lowercase)

What is the Pythonic way to return every nth block of m elements? Note that this is different from just returning every nth element.

Desired result of taking every 1st of 3 blocks of 3 elements (take 3, skip 6, take 3, skip 6...):

['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

I can get to this as:

import itertools
s1 = a[::9]
s2 = a[1::9]
s3 = a[2::9]    
res = list(itertools.chain.from_iterable(zip(s1,s2, s3)))

Is there a cleaner way?

Brad Solomon
  • 38,521
  • 31
  • 149
  • 235

6 Answers6

3

For a fixed order of select and skip, you can wrap indices taking the modulo on the total length of the window (9 here) and select only those beneath the given threshold, 3:

lst = [x for i, x in enumerate(a) if i % 9 < 3]
print(lst)
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

You can make this into a function that makes it more intuitive to use:

def select_skip(iterable, select, skip):
    return [x for i, x in enumerate(iterable) if i % (select+skip) < select]  

print(select_skip(a, select=3, skip=6))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
Nick T
  • 25,754
  • 12
  • 83
  • 121
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • 1
    Clever idea, but it takes a couple of brain cycles to see how the 'skip' is correct (6 == 9 - 3). Might be better to wrap this logic in a helper function or generator expression. – wim Sep 18 '17 at 15:31
  • @wim Yeah, should be more intuitive in a function. – Moses Koledoye Sep 18 '17 at 15:40
2

Perhaps just writing a simple generator is the most readable

def thinger(iterable, take=3, skip=6):
    it = iter(iterable)
    try:
        while True:
            for i in range(take):
                yield next(it)
            for i in range(skip):
                next(it)
    except StopIteration:
        return

This has the advantage of working even if the input is infinite, or not slicable (e.g. data coming in from a socket).

wim
  • 338,267
  • 99
  • 616
  • 750
  • I think it may be the most approachable solution, maybe. I don't think it is the most readable in any way. Reading function names is usually nicer than reading syntax, and it is a more useful tool for a programmer in the future. – Reut Sharabani Sep 18 '17 at 15:24
2

more_itertools is a third-party library that implements itertools recipes and other helpful tools such as more_itertools.windowed.

>  pip install more_itertools

Code

import string

from more_itertools import windowed, flatten


m, n = 3, 6
list(flatten(windowed(string.ascii_lowercase, m, step=m+n)))
# ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']

windowed naturally steps one position per iteration. Given a new step by advancing beyond the overlaps (m), the windows are appropriately determined.

pylang
  • 40,867
  • 14
  • 129
  • 121
0

You can do it using some generic "chunks" recipe:

windows = chunks(original_iter, n=3)

Now that you've windowed you're data as you think of it, use islice's second variant for its' 'step' capabilities:

# flattens the list as well using chain
result = chain.from_iterable(islice(windows, 0, None, 2))
Reut Sharabani
  • 30,449
  • 6
  • 70
  • 88
0

You can use a list comprehension and create a function that does this for any skip, take and list values:

import string
import itertools
a = list(string.ascii_lowercase)
def everyNthBlock(a, take, skip):
  res = [a[i:i + take] for i in range(0, len(a) ,skip + take)]
  return list(itertools.chain(*res))

print(everyNthBlock(a, 3, 6))
#^^^^ => ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
print(everyNthBlock(a, 4, 7))
#^^^^ => ['a', 'b', 'c', 'd', 'l', 'm', 'n', 'o', 'w', 'x', 'y', 'z']
DjaouadNM
  • 22,013
  • 4
  • 33
  • 55
0

Using incomprehensible list comprehension :D

m, n = 3, 3
[elem for blockstart in range(0, len(a), m*n) for elem in a[blockstart:blockstart+n]]    
#> ['a', 'b', 'c', 'j', 'k', 'l', 's', 't', 'u']
Jeronimo
  • 2,268
  • 2
  • 13
  • 28