1

I want to count the 0s between two 1s from a list. For example:

l = [0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0]

I want the output to be [4,2,1]. How can I do that in python?

Park
  • 2,446
  • 1
  • 16
  • 25
vidathri
  • 101
  • 1
  • 9

6 Answers6

1

A slightly different way using itertools.groupby - using the fact that any entries beyond the first and last 1 is irrelevant to us

from itertools import groupby

first_one = l.index(1) # index of the first "1"
last_one = len(l) - l[::-1].index(1) - 1 # index of the last "1"
out = [len(list(g)) for k, g in groupby(l[first_one:last_one], key=lambda x: x == 0) if k]

Output

[4, 2, 1]
Mortz
  • 4,654
  • 1
  • 19
  • 35
  • 1
    That answer is the only one that could run with low memory on a billion element list (well, with my "Rube Goldberg" version, which is quite similar, except that I suffered way more to remove 1st and last element). Because it is just an iterator, not a list. Or at least, it could be, just replacing the outer brackets by parenthesis in the last line. Of course, that advantage is void if what we need at the end is to build a list from an iterator. But there are many situation where we don't. For example if we just want to compute longest sequence, or number of length-3 sequences... – chrslg Nov 09 '22 at 15:41
  • I am not sure how memory efficient `l[::-1]` method is at finding the last index in a billion element list though :) – Mortz Nov 09 '22 at 15:56
  • Oh, yeah, you're right. I missed that part. Especially if `l` is an iterator itself. Then, it that case, we are stuck with my Rube machine. Which may not be that bad after all :D – chrslg Nov 09 '22 at 16:00
0

One option using :

l = [0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0]

s = pd.Series(l)

out = (s[s.eq(0)&s.cummax()&s.loc[::-1].cummax()]
       .rsub(1).groupby(s.ne(0).cumsum()).sum()
       .tolist()
      )

With pure python and itertools.groupby:

from itertools import groupby

l = [0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0]

out = []
start = False
for k, g in groupby(l):
    if k == 1:
        if start:
            out.append(count)
        start = True
    else:
        count = len(list(g))

output: [4, 2, 1]

mozway
  • 194,879
  • 13
  • 39
  • 75
0

How about this, explanation is all in the code:

l = [0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0]
output = []

for i in range(len(l)): #this goes through every index in l (1,2,3,...15, 16)
  if l[i] == 1: #if in the list at the current index is a 1 it
    zeros = 0 #sets zeros to 0
    while l[i+1+zeros] == 0: #and starts a while loop as long as the current index+1+'the amount of zeros after the last number 1' is a zero. (so it stops when it reaches another 1)
      zeros += 1 # because the while loop still runs it adds another 0
      if i+1+zeros == len(l): #the current index + 1 + 'the amount of zeros' = the length of our list
        zeros = 0 # it sets zeros back to 0 so the program doesn't add them to the output (else the output would be [4, 2, 1, 1])
        break #breaks out of the loop
    if zeros > 0: #if the zeros counted between two 1s are more then 0:
      output.append(zeros) # it adds them to our final output
    
print(output) #prints [4, 2, 1] to the terminal
B. de Jong
  • 27
  • 9
0

An old-school answer.

def count(l: list[int]) -> list[int]:
    res = []
    counter = 0
    lastx = l[0]
    for x in l[1:]:
        rising = (x-lastx) > 0
        if rising and counter != 0:
            res.append(counter)
        counter = counter+1 if x==0 else 0
        lastx = x
    return res

count([0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0])  # [4, 2, 1]
0x0fba
  • 1,520
  • 1
  • 1
  • 11
0

My one-liner

Just for fun (not that I encourage doing so in real project), here is a one liner (but a big line), using almost all iterators in itertools (well, not nearly, in reality. There are really lot of them)

(y for (x,y),(z,t) in itertools.pairwise(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))) if x==0)

It's an iterator, and nowhere in the process do I build any list. So it would work f l was itself an iterator giving billions of 1 and 0, without using any memory

Explanation

itertools.groupby(l)

is an iterator giving subiterators for each new value of l. So

for v,it in itertools.groupby(l):
    for x in it:
        print(x)

Just prints all elements of l. But with 9 iterations for x in it, one in which x is 1 time 0, then one in which x is 1 time 1, then one in which x is 4 times 0, then etc.

If y is an iterator, then sum(1 for _ in y) is the number of iterations.

So

((x,sum(1 for _ in y)) for x,y in itertools.groupby(l))

iterates pairs (value0or1, numberOfSuchInGroup), with alternating value0or1

For example

list((x,sum(1 for _ in y)) for x,y in itertools.groupby(l))

here is

[(0, 1), (1, 1), (0, 4), (1, 3), (0, 2), (1, 1), (0, 1), (1, 2), (0, 1)]

If want to drop the first pair, at least if it is a group of 0, since leading 0 does not count. Plus, I want to play with another iterator, which is dropwhile. So

itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))

is the same iterator as before. But without the first pair if it is group of 0

list(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l))))

is

[(1, 1), (0, 4), (1, 3), (0, 2), (1, 1), (0, 1), (1, 2), (0, 1)]

I also want do drop the last pair (at least if it is a group of 0, but it doesn't hurt if I drop it also if it is a group of 1). And I haven't played with pairwise yet. Which iterates through pairs of subsequent elemnents

list(itertools.pairwise(range(5)))

is

((0,1),(1,2),(2,3),(3,4))

for example

Here, I use it for a very silly reason: just to drop the last item, since of course, there is one less item in pairwise iteration. In my last example, we have 4 items

list(x for x,y in itertools.pairwise(range(5)))

is

[0,1,2,3]

So, strange usage of pairwise, but it drops the last iteration used that way.

So in our case

((x,y) for (x,y),(z,t) in itertools.pairwise(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))))

is the same iterator as before, but without the last pair

list((x,y) for (x,y),(z,t) in itertools.pairwise(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))))

is

[(1, 1), (0, 4), (1, 3), (0, 2), (1, 1), (0, 1), (1, 2)]

Now that we have only groups of 0 that are valid, we can filter out the 1s

list((x,y) for (x,y),(z,t) in itertools.pairwise(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))) if x==0)

is

[(0, 4), (0, 2), (0, 1)]

Plus, we don't need the 0s because at this stage they are all 0 anyway. So, keep just y not (x,y)

list(y for (x,y),(z,t) in itertools.pairwise(itertools.dropwhile(lambda x: x[0]==0, ((x,sum(1 for _ in y)) for x,y in itertools.groupby(l)))) if x==0)

Is

[4,2,1]
chrslg
  • 9,023
  • 5
  • 17
  • 31
0

This is a simple version of the code:

newstr=[0,1,0,0,0,0,1,1,1,0,0,1,0,1,1,0]
output=[]
count=0

#This removes all preceding zeroes so that the first element is always 1
while newstr[0]==0:
    if newstr[0]==0:
        newstr=newstr[1::]

#This counts the number of zeroes
for k in newstr:
    if k==1:
        if count!=0:    #Ignoring count if it is zero
            output.append(count)
        count=0
    if k==0:
        count+=1
print(output)
#Output is [4,2,1]

While Loop - checks if first element is 0
If the first element is 0, The list is sliced from 2nd element to the end

So now we have a list with first element as 1

For Loop - Counts the number of zeroes
If the element is 0 , then count+=1
If the element is 1, then the count is appended to the output list and count is reset to 0 again