'Python Ruler Sequence Generator

I have been struggling for a long time to figure how to define a generator function of a ruler sequence in Python, that follows the rules that the first number of the sequence (starting with 1) shows up once, the next two numbers will show up twice, next three numbers will show up three times, etc.

So what I am trying to get is 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 7 etc.

I understand that the way to do this is to have two separate count generators (itertools.count(1)) and then for every number in one generator yield number from the other generator:

def rul():
    num = itertools.count(1) 
    repeator = itertools.count(1)
    for x in range(next(repeator)):
        yield from num

But if I hit next() on this function, I get back just the regular 1,2,3,4.. sequence...

Any help on this would be appreciated.



Solution 1:[1]

How about itertools.repeat?

import itertools

def ruler():
    num = rep_count = 0
    while True:
       rep_count += 1
       for i in range(rep_count):
           num += 1
           yield from itertools.repeat(num, rep_count)

Solution 2:[2]

You can obtain such a generator without writing your own function using count() and repeat() from itertools:

from itertools import repeat,count

i   = count(1,1)
rul = (n for r in count(1,1) for _ in range(r) for n in repeat(next(i),r))

for n in rul: print(n, end = " ")

# 1 2 2 3 3 4 4 4 5 5 5 6 6 6 7 7 7 7 8 8 8 8 9 9 9 9 10 10 10 10 11 11 ...

Solution 3:[3]

If you want to go all in on itertools, you'll need count, repeat, and chain.

You can group the numbers in your sequence as follows, with each group corresponding to a single instance of repeat:

1          # repeat(1, 1)
2 2        # repeat(2, 2)
3 3        # repeat(3, 2)
4 4 4      # repeat(4, 3)
5 5 5      # repeat(5, 3)
6 6 6      # repeat(6, 3)
7 7 7 7    # repeat(7, 4)
...

So we can define ruler_numbers = chain.from_iterable(map(repeat, col1, col2)), as long as we can define col1 and col2 appropriately.

col1 is easy: it's just count(1).

col2 is not much more complicated; we can group them similarly to the original seqeunce:

1         # repeat(1, 1)
2 2       # repeat(2, 2)
3 3 3     # repeat(3, 3)
4 4 4 4   # repeat(4, 4)
...

which we can also generate using chain.from_iterable and map: chain.from_iterable(map(repeat, count(1), count(1))).

In the end, we get our final result in our best attempt at writing Lisp in Python :)

from itertools import chain, repeat, count
ruler_numbers = chain.from_iterable(
                  map(repeat, 
                      count(1),
                      chain.from_iterable(
                        map(repeat,
                            count(1),
                            count(1)))))

or if you want to clean it up a bit with a helper function:

def concatmap(f, *xs):
    return chain.from_iterable(map(f, *xs))

ruler_numbers = concatmap(repeat, 
                          count(1),
                          concatmap(repeat,
                                    count(1),
                                    count(1)))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Alain T.
Solution 3