4

For every run of x or more consecutive zeros in a list in Python, I would like to del all zeros in the run except for x of them. If x = 0, then delete all zeros.

I was thinking of a Python function that took a list, L, and a number, x, as inputs.

For example, let L = [7, 0, 12, 0, 0, 2, 0, 0, 0, 27, 10, 0, 0, 0, 0, 8].

  • If x = 0, then return L = [7, 12, 2, 27, 10, 8]
  • If x = 1, then return L = [7, 0, 12, 0, 2, 0, 27, 10, 0, 8]
  • If x = 2, then return L = [7, 0, 12, 0, 0, 2, 0, 0, 27, 10, 0, 0, 8]
  • If x = 3, then return L = [7, 0, 12, 0, 0, 2, 0, 0, 0, 27, 10, 0, 0, 0, 8]
  • If x = 4, then return L = [7, 0, 12, 0, 0, 2, 0, 0, 0, 27, 10, 0, 0, 0, 0, 8] (Same as original L)
  • If x >= 5, then return original L as there are no runs of 5 or more consecutive zeros.

Any help would be sincerely appreciated.

jamylak
  • 128,818
  • 30
  • 231
  • 230
b_ron_
  • 197
  • 1
  • 1
  • 10
  • I'm not sure there's a pithy one liner for this. You may have to loop through the list, counting zeros as you go, and deleting excess zeros as you find them. – Max Jul 31 '12 at 03:14
  • 1
    @Max, you could write my answer in one line. It'd be a bit more than 80 characters though :) – John La Rooy Jul 31 '12 at 03:23

3 Answers3

8

This is easy to do as a generator. Wrap your call to it in a list constructor if you want a fresh list with the zero-runs removed.

def compact_zero_runs(iterable, max_zeros):
    zeros = 0
    for i in iterable:
        if i == 0:
            zeros += 1
            if zeros <= max_zeros:
                yield i
        else:
            zeros = 0
            yield i
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • This generator is better than what I came up with. (+1) – mgilson Jul 31 '12 at 03:22
  • @Blckknght, would you mind verifying that I understand correctly? The generator function takes the values of iterable checks to see if they are 0 or not. If zero, then increment zeros and check whether max_zeros has been reached. If max_zeros has been reached then yield that value in iterable. Otherwise start the for loop over again. If value in iterable is not zero, then count of zeros remains 0 and yield that value in iterable. To show list, define this generator outside of main function, then call to it within original function (that I was working on) by LL=list(c_z_r(L,2)) (say)? – b_ron_ Aug 01 '12 at 02:19
  • @b_ron_, I think you have two bits slightly wrong. First, if the `zeros` count has exceeded `max_zeros`, it does not yield the value (these are the excess zeros that get dropped). It's the ones before you exceed the limit that are yielded. The other point is that when you hit a non-zero value it always resets the count of zeros to 0, forgetting how many had come before (since you only need to count consecutive runs of zeros). Your syntax for getting a list from the generator is exactly right. – Blckknght Aug 01 '12 at 06:09
3

Using groupby:

def del_zeros(lst, n):
    lst = (list(j)[:n] if i else list(j) 
           for i,j in itertools.groupby(lst, key=lambda x:x==0))

    return [item for sublist in lst for item in sublist]

And the tests:

>>> [del_zeros(L, i) for i in range(5)]
[[7, 12, 2, 27, 10, 8],
 [7, 0, 12, 0, 2, 0, 27, 10, 0, 8],
 [7, 0, 12, 0, 0, 2, 0, 0, 27, 10, 0, 0, 8],
 [7, 0, 12, 0, 0, 2, 0, 0, 0, 27, 10, 0, 0, 0, 8],
 [7, 0, 12, 0, 0, 2, 0, 0, 0, 27, 10, 0, 0, 0, 0, 8]]
JBernardo
  • 32,262
  • 10
  • 90
  • 115
3
from itertools import groupby, chain, islice
from functools import partial
from operator import eq

def f(L, x):
    groups = groupby(L, partial(eq, 0))
    return list(chain.from_iterable(islice(v, x) if k else v for k,v in groups))
John La Rooy
  • 295,403
  • 53
  • 369
  • 502