13

What is pythons equivalent of Ruby's each_slice(count)?
I want to take 2 elements from list for each iteration.
Like for [1,2,3,4,5,6] I want to handle 1,2 in first iteration then 3,4 then 5,6.
Ofcourse there is a roundabout way using index values. But is there a direct function or someway to do this directly?

Nakilon
  • 34,866
  • 14
  • 107
  • 142
theReverseFlick
  • 5,894
  • 8
  • 32
  • 33
  • 1
    mark's answer completely satisfies the specifications you provided in your question. However, it is important to note that the behavior he specified deviates from ruby's each_slice: If the last slice is shorter than the rest it will be padded with fillvalue, whereas in ruby's each_slice it is merely a shortened array. If you want this shortened list/iterable behavior, then Mark's answer won't work. – bwv549 Aug 24 '15 at 16:39

6 Answers6

10

There is a recipe for this in the itertools documentation called grouper:

from itertools import izip_longest
def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Use like this:

>>> l = [1,2,3,4,5,6]
>>> for a,b in grouper(2, l):
>>>     print a, b

1 2
3 4
5 6
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
5

I know this has been answered by multiple experts on the language, but I have a different approach using a generator function that is easier to read and reason about and modify according to your needs:

def each_slice(list: List[str], size: int):
    batch = 0
    while batch * size < len(list):
        yield list[batch * size:(batch + 1) * size]
        batch += 1   

slices = each_slice(["a", "b", "c", "d", "e", "f", "g"], 2)
print([s for s in slices])

$ [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g']]

If you need each slice to be of batch size, maybe pad None, or some default character you can simply add padding code to the yield. If you want each_cons instead, you can do that by modifying the code to move one by one instead of batch by batch.

nurettin
  • 11,090
  • 5
  • 65
  • 85
  • I noticed @bwv549's answer, but mine uses the existing array slice syntax instead of creating an array and appending to it. – nurettin Dec 28 '18 at 12:24
4

Duplicates ruby's each_slice behavior for a small trailing slice:

def each_slice(size, iterable):
    """ Chunks the iterable into size elements at a time, each yielded as a list.

    Example:
      for chunk in each_slice(2, [1,2,3,4,5]):
          print(chunk)

      # output:
      [1, 2]
      [3, 4]
      [5]
    """
    current_slice = []
    for item in iterable:
        current_slice.append(item)
        if len(current_slice) >= size:
            yield current_slice
            current_slice = []
    if current_slice:
        yield current_slice

The answers above will pad the last list (i.e., [5, None]), which may not be what is desired in some cases.

bwv549
  • 5,243
  • 2
  • 23
  • 20
2

Same as Mark's but renamed to 'each_slice' and works for python 2 and 3:

try:
    from itertools import izip_longest  # python 2
except ImportError:
    from itertools import zip_longest as izip_longest  # python 3

def each_slice(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)
bwv549
  • 5,243
  • 2
  • 23
  • 20
0

An improvement on the first two: If the iterable being sliced is not exactly divisible by n, the last will be filled to the length n with None. If this is causing you type errors, you can make a small change:

def each_slice(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    raw = izip_longest(fillvalue=fillvalue, *args)
    return [filter(None, x) for x in raw]

Keep in mind this will remove ALL None's from the range, so should only be used in cases where None will cause errors down the road.

Shay
  • 33
  • 4
0
s_size = 4
l = list(range(100))

while len(l) > 0:
    slice = [l.pop() for _e,i in enumerate(l) if i <= s_size ]
    print(slice)
vsuzdaltsev
  • 1
  • 1
  • 1