[Tutor] Consecutive_zeros

Martin A. Brown martin at linux-ip.net
Sun Jul 3 22:15:58 EDT 2022


Hello there,

> For example, given the string:
> Its failing on below test case
> 
> print(consecutive_zeros("0"))
> It should return 1. Returns 0
> I get the max(length) as 1, if I print it separately

There are many ways to this sort of thing, and I think Mats has 
already responded that your function (below) looks good aside from 
your boundary case.  Of course, boundary cases are one of the things 
that software quality assurance and test tooling are there to help 
you discover.

If your code never hits the boundary condition, then you'll always 
sleep soundly.  But, as an excellent systems programmer I worked 
with used to say:  "I like to think that my software inhabits a 
hostile universe."  It's a defensive programming mindset that I have 
also tried to adopt.

> def consecutive_zeros(string):
>     zeros = []
>     length = []
>     result = 0
>     for i in string:
>         if i == "0":
>             zeros.append(i)        else:
>             length.append(len(zeros))
>             zeros.clear()
>             result = max(length)
>     return result

> Goal: analyze a binary string consisting of only zeros and ones. Your code
> should find the biggest number of consecutive zeros in the string.

I took a look at the problem statement and I thought immediately of 
the itertools.groupby function [0].  While this is not generally one 
of the Python modules that you'd encounter at your first exposure to 
Python, I thought I'd mention it to illustrate that many languages 
provide advanced tooling that allow you to solve these sorts of 
computer science problems. There's no substitute for knowing how to 
write or apply this yourself. There's also value in knowing what 
other options are available to you in the standard library.  (Like 
learning when to use a rasp, chisel, smoothing plane or sandpaper 
when working with wood:  All work.  Some work better in certain 
situations.)

The itertools module is fantastic for dealing with streams and 
infinite sequences.

This function (and others in this module) useful for stuff like your 
specific question, which appears to be a string that probably fits 
neatly into memory.

  def consecutive_zeros(s):
      zcount = 0
      for char, seq in itertools.groupby(s):
          if char != '0':
              continue
          t = len(list(seq))
          zcount = max((t, zcount))
      return zcount, s

I am mentioning this particular function not because I have any 
specific deep experience with the itertools module, but to share 
another way to think about approaching any specific problem.  I have 
never regretted reading in the standard libraries of any language 
nor the documentation pages of any system I've ever worked on.

In this case, your question sounded to me closer to a "pure" 
computer science question and in the vein of functional programming 
and itertools jumped directly into my memory.

One exercise I attempt frequently is to move a specific question, 
e.g. "count the number of zeroes in this string sequence" to a more 
general case of "give me the size of the longest repeated 
subsequence of items".  This often requires keeping extra data (e.g.
the collections.defaultdict) but means you sometimes have other 
answers at the ready.  In this case, you could also easily answer 
the question of 'what is the longest subsequence of "1"s' as well.

And, in keeping with the notion of testing* (as Mats has suggested) 
and worrying about boundaries, I include a toy program (below) that 
demonstrates the above function as well as an alternate, generalized 
example, as well as testing the cases in which the result is known 
ahead of time.

Best of luck,

-Martin

   * Note, what I did is not quite proper testing, but is a simple 
     illustration of how one could do it.  Using assert is a 
     convenient way to work with test tooling like py.test.

 [0] https://docs.python.org/3/library/itertools.html#itertools.groupby


#! /usr/bin/python
#
# -- response to Consecutive_zeros question

import os
import sys
import random
import itertools
import collections


def consecutive_items(s):
    counts = collections.defaultdict(int)
    for item, seq in itertools.groupby(s):
        counts[item] = max(sum(1 for _ in seq), counts[item])
    return counts, s


def consecutive_char_of_interest(s, item):
     counts, _ = consecutive_items(s)
     return counts[item], s


def consecutive_zeros(s):
    return(consecutive_char_of_interest(s, '0'))


# def consecutive_zeros(s):
#      zcount = 0
#      for char, seq in itertools.groupby(s):
#          if char != '0':
#              continue
#          t = len(list(seq))
#          zcount = max((t, zcount))
#      return zcount, s


def report(count, sample):
    sample = (sample[:50] + '...') if len(sample) > 75 else sample
    print('{:>9d} {}'.format(count, sample))


def fail_if_wrong(sample, correct):
    count, _ = consecutive_zeros(sample)
    assert count == correct
    report(count, sample)
    return count, sample


def cli(fin, fout, argv):
    fail_if_wrong('a', 0)          # - feed it "garbage"
    fail_if_wrong('Q', 0)          # - of several kinds
    fail_if_wrong('^', 0)          # - punctuation! What kind of planet?!
    fail_if_wrong('1', 0)          # - check boundary
    fail_if_wrong('0', 1)          # - check boundary
    fail_if_wrong('01', 1)         # - oh, let's be predictable
    fail_if_wrong('001', 2)
    fail_if_wrong('0001', 3)
    fail_if_wrong('00001', 4)
    fail_if_wrong('000001', 5)
    fail_if_wrong('0000001', 6)
    fail_if_wrong('00000001', 7)
    fail_if_wrong('000000001', 8)
    n = 37
    fail_if_wrong('0' * n, n)      # - favorite number?
    n = 1024 * 1024                # - some big number
    fail_if_wrong('0' * n, n)
    n = random.randint(9, 80)
    fail_if_wrong('0' * n, n)      # - let the computer pick

    report(*consecutive_zeros(''.join(random.choices('01', k=60))))
    report(*consecutive_zeros(''.join(random.choices('01', k=60))))
    report(*consecutive_zeros(''.join(random.choices('01', k=1024))))
    report(*consecutive_zeros(''.join(random.choices('01', k=1024*1024))))

    return os.EX_OK


if __name__ == '__main__':
    sys.exit(cli(sys.stdin, sys.stdout, sys.argv[1:]))

# -- end of file



-- 
Martin A. Brown
http://linux-ip.net/


More information about the Tutor mailing list