Split a string into chunks of substrings with successively increasing length

Viewed 216

Let's say I have this string:

a = 'abcdefghijklmnopqrstuvwxyz'

And I want to split this string into chunks, like below:

['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

so that every chunk has a different number of characters. For instance, the first one should have one character, the second two and so on.

If there are not enough characters in the last chunk, then I need to add spaces so it matches the length.

I tried this code so far:

print([a[i: i + i + 1] for i in range(len(a))])

But it outputs:

['a', 'bc', 'cde', 'defg', 'efghi', 'fghijk', 'ghijklm', 'hijklmno', 'ijklmnopq', 'jklmnopqrs', 'klmnopqrstu', 'lmnopqrstuvw', 'mnopqrstuvwxy', 'nopqrstuvwxyz', 'opqrstuvwxyz', 'pqrstuvwxyz', 'qrstuvwxyz', 'rstuvwxyz', 'stuvwxyz', 'tuvwxyz', 'uvwxyz', 'vwxyz', 'wxyz', 'xyz', 'yz', 'z']

Here is my desired output:

['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']
7 Answers

I don't think any one liner or for loop will look as elegant, so let's go with a generator:

from itertools import islice, count

def get_increasing_chunks(s):
    it = iter(s)
    c = count(1)

    nxt, c_ = next(it), next(c)
    while nxt:
        yield nxt.ljust(c_)
        nxt, c_ = ''.join(islice(it, c_+1)), next(c)

    return out
[*get_increasing_chunks(a)]
# ['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

Thanks to @Prune's comment, I managed to figure out a way to solve this:

a = 'abcdefghijklmnopqrstuvwxyz'
lst = []
c = 0
for i in range(1, len(a) + 1):
    c += i
    lst.append(c)
print([a[x: y] + ' ' * (i - len(a[x: y])) for i, (x, y) in enumerate(zip([0] + lst, lst), 1) if a[x: y]])    
    

Output:

['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

I find the triangular numbers than do a list comprehension, and add spaces if the length is not right.

so what you need is to have a number that controls how many characters you're going to grab (in this case the amount of iterations), and a second number that remembers what the last index was, plus one last number to tell where to stop.

my_str = "abcdefghijklmnopqrstuvwxyz"
last_index = 0
index = 1
iter_count = 1

while True:
    sub_string = my_str[last_index:index]
    print(sub_string)
    last_index = index
    iter_count += 1
    index = index + iter_count
    if last_index > len(my_str):
        break

note that you don't need the while loop. i was just feeling lazy

It seems like the split_into recipe at more_itertools can help here. This is less elegant than the answer by @cs95, but perhaps this will help others discover the utility of the itertools module.

Yield a list of sequential items from iterable of length ‘n’ for each integer ‘n’ in sizes.

>>> list(split_into([1,2,3,4,5,6], [1,2,3]))
[[1], [2, 3], [4, 5, 6]]

To use this, we need to construct a list of sizes like [1, 2, 3, 3, 5, 6, 7].

import itertools

def split_into(iterable, sizes):
    it = iter(iterable)
    for size in sizes:
        if size is None:
            yield list(it)
            return
        else:
            yield list(itertools.islice(it, size))

a = 'abcdefghijklmnopqrstuvwxyz'

sizes = [1]
while sum(sizes) <= len(a):
    next_value = sizes[-1] + 1
    sizes.append(next_value)
# sizes = [1, 2, 3, 4, 5, 6, 7]

list(split_into(a, sizes))

# [['a'],
#  ['b', 'c'],
#  ['d', 'e', 'f'],
#  ['g', 'h', 'i', 'j'],
#  ['k', 'l', 'm', 'n', 'o'],
#  ['p', 'q', 'r', 's', 't', 'u'],
#  ['v', 'w', 'x', 'y', 'z']]

chunks = list(map("".join, split_into(a, sizes)))
# ['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz']

# Pad last item with whitespace.
chunks[-1] = chunks[-1].ljust(sizes[-1], " ")
# ['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

Here is a solution using accumulate from itertools.

>>> from itertools import accumulate
>>> from string import ascii_lowercase

>>> s = ascii_lowercase
>>> n = 0
>>> accum = 0
>>> while accum < len(s):
    n += 1
    accum += n

>>> L = [s[j:i+j] for i, j in enumerate(accumulate(range(n)), 1)]

>>> L[-1] += ' ' * (n-len(L[-1]))
>>> L
['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

Update: Could also be obtained within the while loop

n = 0
accum = 0
L = []
while accum < len(s):
    n += 1
    L.append(s[accum:accum+n])
    accum += n

['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz']

Adding a little to U11-Forward's answer:

a = 'abcdefghijklmnopqrstuvwxyz'
l = list(range(len(a))) # numberes list / 1 to len(a)
triangular = [sum(l[:i+2]) for i in l] # sum of 1, 2 and 1,2,3 and 1,2,3,4 and etc
print([a[x: y].ljust(i, ' ') for i, (x, y) in enumerate(zip([0] + triangular, triangular), 1) if a[x: y]])

Output:

['a', 'bc', 'def', 'ghij', 'klmno', 'pqrstu', 'vwxyz  ']

Find the triangular numbers, do a list comprehension and fill with spaces if the length is incorrect.

a = 'abcdefghijklmnopqrstuvwxyz'
inc = 0
output = []
for i in range(0, len(a)):
    print(a[inc: inc+i+1])
    inc = inc+i+1
    if inc > len(a):
        break
    output.append(a[inc: inc+i+1])        
        
print(output)

Hey, here is the snippet for your required output. I have just altered your logic. Output:

['b', 'de', 'ghi', 'klmn', 'pqrst', 'vwxyz']
Related