for-loop I implemented is out of range - not sure what the problem is

Viewed 77
lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']
if(len(lst)%3==0):
    pointer = 0
    lst1 = []
    for i in lst:
        for j in range(pointer, pointer+3):
            lst1.append(lst[j])
            print(lst1)
        pointer += 3

else:
    counter = 3
    pointer = 0
    lst1 = []
    for i in lst:
        for j in range(pointer, pointer+3):
            lst1.append(lst[j])
            counter -= 1
        pointer+=3
        if (counter != 0):
            lst1.append(lst)
            pointer+=1
            counter-=1
    print('nothing')

The error is related to the lst1.append(lst[j]) which is out of range, but I can not find any other way to fix it. The code is supposed to take a list if it is divisible by 3, divide it into batches with 3 components, and if not, take some element from the beginning of the list.

4 Answers

One approach is to use itertools.cycle to pad the last group.

from itertools import cycle
from math import ceil

lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']
e = cycle(lst)
[[next(e), next(e), next(e)] for _ in range(ceil(len(lst)/3))]

# [['a', 'b', 'c'], ['d', 'e', 'i'], ['u', 'y', 'l'], ['p', 'a', 'b']]

Your issue is that you didnt guard against pointer going over the boundary of the list lst. To help you see what's going on I would add some print statements like print(f"pointer={pointer} len={len(lst)}")

As a side note, I would simplify this logic a bit. You can make one good method for pulling a list of three out of the larger list given a start point, and then one loop that calls this method. Try something like this:

def get_sublist(lst, pointer, size):
    result = []
    for i in range(pointer, pointer + size):
        # Notice the mod avoids going over the boundary
        result.append(lst[i % len(lst)])
    return result


def get_sublist_groups(lst, size):
    # The last value in range lets you count by `n`. in this case 3
    result = []
    for pointer in range(0, len(lst), size):
        result.append(get_sublist(lst, pointer, size))
    return result


lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']
print(get_sublist_groups(lst, 3))
[['a', 'b', 'c'], ['d', 'e', 'i'], ['u', 'y', 'l'], ['p', 'a', 'b']]

Bonus: This could also be refactored using list comprehensions:

lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']
size = 3
result = [
    [lst[j % len(lst)] for j in range(i, i + size)]
    for i in range(0, len(lst), size)
]
print(result)

You are getting that error because pointer+3 at one point will exceed the list size and throw an indexing error since the index lst[j] will be greater than len(lst) and therefore not exist. You can achieve the solution by slicing.

lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']

lst_to_batches_of = 3
for i in range(0,len(lst),lst_to_batches_of): # i will increment in steps, in this case 3
    lst1 = lst[i:i+lst_to_batches_of] # slice the list into the required batches
    print(lst1)
    if len(lst1)!=lst_to_batches_of: # if the final list is short, add values from the beginning, if not then this statement is avoided
        print(lst1 + lst[0:(lst_to_batches_of-len(lst1))]) # add the number of elements required from the start

A solution based on a recipe in the Python's itertools docs:

from itertools import zip_longest, chain, islice

def grouper(iterable, n):
    mod_n = len(iterable) % n
    if mod_n:
        to_iter_in = chain(iterable, islice(iterable, 0, n - mod_n))
    else:
        to_iter_in = iterable
    args = [iter(to_iter_in)] * n
    return zip_longest(*args)

This function returns an iterator, that yields tuples of size n (the size to the group) lazily. It requires that the iterable support len().

To build a list of lists using list comprehensions:

lst = ['a', 'b', 'c', 'd', 'e', 'i', 'u', 'y', 'l', 'p']

print([list(x) for x in grouper(lst, 3)])
output:
[['a', 'b', 'c'], ['d', 'e', 'i'], ['u', 'y', 'l'], ['p', 'a', 'b']]

print([list(x) for x in grouper(lst, 4)])
output:
[['a', 'b', 'c', 'd'], ['e', 'i', 'u', 'y'], ['l', 'p', 'a', 'b']]

Also the grouper function allow to you to iterate over this new structure without building the list. Check this:

for x, y, z in grouper(lst, 3):
    print(f"x: {x}, y: {y}, z: {z}")

output:
x: a, y: b, z: c
x: d, y: e, z: i
x: u, y: y, z: l
x: p, y: a, z: b

Also, if you need to iterate over the groups and build a list in every group, you can use map:

for a in map(list, grouper(lst, 3)):
    print(a)

output:
['a', 'b', 'c']
['d', 'e', 'i']
['u', 'y', 'l']
['p', 'a', 'b']
Related