7

Can't seem to find a clue to this online and can't figure it out myself so:

How would I go about slicing a list so that I return a list of slices of contiguous non-zero integers. ie:

data = [3, 7, 4, 0, 1, 3, 7]

and I want to produce:

slices = [[3, 7, 4], [1, 3, 7]]

I have tried various methods of iterating through the list, have been leaning towards a generator that lets me know when the contiguous groups start and stop, by testing if there is a 0 before or after, but then I am a bit stumped.

emergence
  • 405
  • 3
  • 10
  • 1
    Related: [Extract separate non-zero blocks from array](http://stackoverflow.com/questions/31544129/extract-separate-non-zero-blocks-from-array) – user2314737 Jan 10 '17 at 23:15

5 Answers5

15
import itertools
[ list(x[1]) for x in itertools.groupby(data, lambda x: x == 0) if not x[0] ]
Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • I'm going to have to read this for a while to understand exactly what's going on, but it works perfectly and is amazingly succinct! – emergence Jul 20 '11 at 11:29
  • 1
    I think i've got it...itertools.groupby produces grouper objects that slice whenever the condition lambda x: x==0 is met in data, and then if not x[0] filters out all the ones it wasn't met on. Correct? – emergence Jul 20 '11 at 11:45
  • 2
    almost. groupby slices whenever the key value(returned by the lambda) changes (so in this example if there are consecutive zeros, it will group them) – Karoly Horvath Jul 20 '11 at 12:01
6

Look at itertools.groupby:

>>> data = [3, 7, 4, 0, 1, 3, 7, 4, 0, 5]
>>> a=[list(i[1]) for i in itertools.groupby(data, key=lambda i:i==0)]
>>> a
[[3, 7, 4], [0], [1, 3, 7, 4], [0], [5]]
>>> [i for i in a if i != [0]]
[[3, 7, 4], [1, 3, 7, 4], [5]]
utdemir
  • 26,532
  • 10
  • 62
  • 81
3
def split_on_zero(data):
    start = 0
    for (i, n) in enumerate(data):
        if n == 0:
            yield data[start:i]
            start = i + 1
    yield data[start:]

>>> list(split_on_zero([3, 7, 4, 0, 1, 3, 7]))
[[3, 7, 4], [1, 3, 7]]
>>> list(split_on_zero([0, 1, 2, 0, 3, 4, 5, 0]))
[[], [1, 2], [3, 4, 5], []]
Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • The final yield does not see `i`. It should be just `yield data[start:]`. This will return [] element between consecutive 0s and if the array starts/ends with 0. – Jan Hudec Jul 20 '11 at 11:12
  • @Jan: Good catch! Actually, you're analysis is incorrect: the variable is visible after the for-loop. But you are right that it's unnecessary. – Marcelo Cantos Jul 20 '11 at 11:15
1

Here the very base solution you can try to use too:

data = [1, 0, 3, 7, 4, 1, 3, 7]

def get_zero_sliced(inputList):
    res = []
    prev = 0
    for i,x in enumerate(data):
        if x == 0 and i != 0:
            res.append(data[prev:i])
            prev = i + 1
        if i == len(data)-1 and prev != i:
            res.append(data[prev:])
    return res

get_zero_sliced(data)
Artsiom Rudzenka
  • 27,895
  • 4
  • 34
  • 52
  • Premature optimization (checking that it contains no zeroes), neither warranted (were you told it's a most common case? no? so don't optimize for it!) and probably neither actually helpful (since you need extra pass). – Jan Hudec Jul 20 '11 at 12:10
0

A generator will work. Here's one, but there's probably other ways to do it. I called it x because I don't have a good name right now (good naming takes some thought, so give it some thought).

def x(iterator):
    result = []
    for i in iterator:
        if i != 0:
            result.append(i)
        elif result:
            yield result
            result = []
    if result: yield result
camh
  • 40,988
  • 13
  • 62
  • 70