Itertools Recipe: All Equal

The itertools docs has a ton of slick recipes for using the library to good effect. Some of the code is more useful than illustrative, so I wanted to use these notebooks to break down a few of the functions.

This is

# poor import style, but I want to copy-paste the code
# as-is from the docs

from itertools import *
import itertools

all_equal()

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

Demo

all_equal('aaaaaaaaa')
True
all_equal('aaaaaaab')
False

Why this works

This one relies on how the itertools.groupby() function resolves. Calling it on an iterable yields a stream of tuples of the form (unique group found, iterable).

When they’re all the same, this will be one tuple

obj = itertools.groupby('aaaaaaa')

list(obj)
[('a', <itertools._grouper at 0x25db4853cc0>)]
list(obj)
[('a', <itertools._grouper at 0x1e531b6bf28>)]

and when it gets a different value on the end, it’s got two

obj = itertools.groupby('aaaaaab')

list(obj)
[('a', <itertools._grouper at 0x25db486d240>),
 ('b', <itertools._grouper at 0x25db486d2e8>)]

the default behavior of the next() function is what ties this all together. Calling next() on an iterable with a second argument is very similar to the behavior of dict.get(default_val)

# without default
try:
    obj = iter([])
    print(next(obj))
except StopIteration:
    print('Empty Iter')
    
# with default
try:
    obj = iter([])
    print(next(obj, 0))
except StopIteration:
    print('Empty Iter')
    
Empty Iter
0

So in this context, lets look at that second chunk

and not next(g, False)

If we only have one tuple returned by groupby(), next(g) will raise the StopIterationException, thus giving us the default value False. Applying the leading not evaluates the statement to True.

Otherwise, if it weren’t empty, anything that gets returned will be “Truthy”, get the not applied to it and evaluate to False.