Count consecutive characters

How would I count consecutive characters in Python to see the number of times each unique digit repeats before the next unique digit?

At first, I thought I could do something like:

word = '1000'

counter=0
print range(len(word))


for i in range(len(word)-1):
    while word[i]==word[i+1]:
        counter +=1
        print counter*"0"
    else:
        counter=1
        print counter*"1"

So that in this manner I could see the number of times each unique digit repeats. But this, of course, falls out of range when i reaches the last value.

In the example above, I would want Python to tell me that 1 repeats 1, and that 0 repeats 3 times. The code above fails, however, because of my while statement.

I know you can do this with just built-in functions and would prefer a solution that way.


Consecutive counts:

Ooh nobody's posted itertools.groupby yet!

s = "111000222334455555"

from itertools import groupby

groups = groupby(s)
result = [(label, sum(1 for _ in group)) for label, group in groups]

After which, result looks like:

[("1": 3), ("0", 3), ("2", 3), ("3", 2), ("4", 2), ("5", 5)]

And you could format with something like:

", ".join("{}x{}".format(label, count) for label, count in result)
# "1x3, 0x3, 2x3, 3x2, 4x2, 5x5"

Total counts:

Someone in the comments is concerned that you want a total count of numbers so "11100111" -> {"1":6, "0":2}. In that case you want to use a collections.Counter:

from collections import Counter

s = "11100111"
result = Counter(s)
# {"1":6, "0":2}

Your method:

As many have pointed out, your method fails because you're looping through range(len(s)) but addressing s[i+1]. This leads to an off-by-one error when i is pointing at the last index of s, so i+1 raises an IndexError. One way to fix this would be to loop through range(len(s)-1), but it's more pythonic to generate something to iterate over.

For string that's not absolutely huge, zip(s, s[1:]) isn't a a performance issue, so you could do:

counts = []
count = 1
for a, b in zip(s, s[1:]):
    if a==b:
        count += 1
    else:
        counts.append((a, count))
        count = 1

The only problem being that you'll have to special-case the last character if it's unique. That can be fixed with itertools.zip_longest

import itertools

counts = []
count = 1
for a, b in itertools.zip_longest(s, s[1:], fillvalue=None):
    if a==b:
        count += 1
    else:
        counts.append((a, count))
        count = 1

If you do have a truly huge string and can't stand to hold two of them in memory at a time, you can use the itertools recipe pairwise.

def pairwise(iterable):
    """iterates pairwise without holding an extra copy of iterable in memory"""
    a, b = itertools.tee(iterable)
    next(b, None)
    return itertools.zip_longest(a, b, fillvalue=None)

counts = []
count = 1
for a, b in pairwise(s):
    ...

A solution "that way", with only basic statements:

word="100011010" #word = "1"
count=1
length=""
if len(word)>1:
    for i in range(1,len(word)):
       if word[i-1]==word[i]:
          count+=1
       else :
           length += word[i-1]+" repeats "+str(count)+", "
           count=1
    length += ("and "+word[i]+" repeats "+str(count))
else:
    i=0
    length += ("and "+word[i]+" repeats "+str(count))
print (length)

Output :

'1 repeats 1, 0 repeats 3, 1 repeats 2, 0 repeats 1, 1 repeats 1, and 0 repeats 1'
#'1 repeats 1'

Totals (without sub-groupings)

#!/usr/bin/python3 -B

charseq = 'abbcccdddd'
distros = { c:1 for c in charseq  }

for c in range(len(charseq)-1):
    if charseq[c] == charseq[c+1]:
        distros[charseq[c]] += 1

print(distros)

I'll provide a brief explanation for the interesting lines.

distros = { c:1 for c in charseq  }

The line above is a dictionary comprehension, and it basically iterates over the characters in charseq and creates a key/value pair for a dictionary where the key is the character and the value is the number of times it has been encountered so far.

Then comes the loop:

for c in range(len(charseq)-1):

We go from 0 to length - 1 to avoid going out of bounds with the c+1 indexing in the loop's body.

if charseq[c] == charseq[c+1]:
    distros[charseq[c]] += 1

At this point, every match we encounter we know is consecutive, so we simply add 1 to the character key. For example, if we take a snapshot of one iteration, the code could look like this (using direct values instead of variables, for illustrative purposes):

# replacing vars for their values
if charseq[1] == charseq[1+1]:
    distros[charseq[1]] += 1

# this is a snapshot of a single comparison here and what happens later
if 'b' == 'b':
    distros['b'] += 1

You can see the program output below with the correct counts:

➜  /tmp  ./counter.py
{'b': 2, 'a': 1, 'c': 3, 'd': 4}

You only need to change len(word) to len(word) - 1. That said, you could also use the fact that False's value is 0 and True's value is 1 with sum:

sum(word[i] == word[i+1] for i in range(len(word)-1))

This produces the sum of (False, True, True, False) where False is 0 and True is 1 - which is what you're after.

If you want this to be safe you need to guard empty words (index -1 access):

sum(word[i] == word[i+1] for i in range(max(0, len(word)-1)))

And this can be improved with zip:

sum(c1 == c2 for c1, c2 in zip(word[:-1], word[1:]))