-1

I'm trying to write a code that could divide a list into smaller lists, as follows, I have a list and a value like that:

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9]
N = 3

I want to get smaller N lists depends on the length of nb_classes and the value of N.

If the value of N is 3 for example, I want to have 3 small lists like that :

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9]

list_1 = [1,2,3]
list_2 = [4,5,6]
list_3 = [7,8,9]

Second example :

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

list_1 = [1,2,3,4]
list_2 = [5,6,7]
list_3 = [8,9,10]

Third example :

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

list_1 = [1,2,3,4]
list_2 = [5,6,7,8]
list_3 = [9,10,11]

Thank you.

ProgrX
  • 111
  • 1
  • 2
  • 15
  • 2
    And what if the length is 11? You will get 4-4-3? – sudden_appearance Apr 04 '22 at 09:33
  • 1
    Yes, thank you I forgot to mention that – ProgrX Apr 04 '22 at 09:35
  • Would you say you want the overflow from the remainder to spill into the first elements of the lists? – Freddy Mcloughlan Apr 04 '22 at 09:46
  • 3
    Surely, for this task divisibility by 3 is more important than divisibility by 2 (i.e., odd/evenness)? – Schnitte Apr 04 '22 at 09:46
  • 1
    I think [this](https://stackoverflow.com/a/4119142/10043695) answers your question. (The `slice_list()` function) – 3nws Apr 04 '22 at 09:47
  • 1
    yes @Schnitte you are right, so how do I do the division according to the value of N – ProgrX Apr 04 '22 at 09:48
  • 1
    If N is 3, and the length of the list is evenly divisible by three, then `list_1 = nb_classes[:(len(nb_classes) / 3)]`, `list_2 = nb_classes[(len(nb_classes) / 3):2*(len(nb_classes) / 3)]`, `list_3 = nb_classes[2*(len(nb_classes) / 3):]` should work. – Schnitte Apr 04 '22 at 09:53

4 Answers4

5

There's a straightforward way using itertools.cycle and itertools.islice

import itertools

def split_list(lst, n=3):
    lengths = [0 for _ in range(n)]
    # set the lengths of each sublist correctly
    for _, idx in zip(range(len(lst)), itertools.cycle(range(n))):
        lengths[idx] += 1

    iterator = iter(lst)

    for length in lengths:
        yield itertools.islice(iterator, length)

Note that this returns a generator expression which produces islice objects, which are themselves iterators not lists. If you actually need lists here, you'll have to make them yourself.

import itertools  # as before

def split_list(lst):
    # as before, until...

    iterator = iter(lst)

    # instead of the for loop leading to the yield, instead use...
    return [list(itertools.islice(iterator, length)) for length in lengths]
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
  • Doesn't produce any lists. – Kelly Bundy Apr 04 '22 at 10:10
  • Could you replace `range(3)` for `range(N)`? and `lengths` to `[0]*N`? – Freddy Mcloughlan Apr 04 '22 at 10:10
  • 1
    @FreddyMcloughlan you could of course, but the question indicated you should always divide into 3. An aside: `[0]*N` can, on occasion, be dangerous -- prefer `[0 for _ in range(N)]`. This won't matter for `[0]` but if your "filler" object is mutable you end up with `N` references to the _same_ object in the multiplication case but not the list comprehension. Try `lst = [[]] * 3; lst[0].append(1); assert lst[1][0] == 1` – Adam Smith Apr 04 '22 at 10:16
  • @KellyBundy true but you know as well as I do that users rarely _actually_ need lists. If they do then collate in the function and return the whole. I'll add that in my answer. – Adam Smith Apr 04 '22 at 10:16
  • 1
    *Maybe* it's not what they need, but that's what the question asks for. And I strongly disagree that people rarely need lists. – Kelly Bundy Apr 04 '22 at 10:19
  • 1
    @KellyBundy that's a fair criticism. I adjusted the answer to add an alternate form that returns the real memory-consuming lists instead :). It's certainly arguable whether folks really need a list, but typically if you're using a list (and not an object or a tuple) then you have a bunch of something and rarely -- at least in my experience -- do you need more than one of them at a time. The only advantage that lists have over iterators in that situation is that you can't exhaust a list! :) – Adam Smith Apr 04 '22 at 10:22
  • 1
    Yes, being able to iterate multiple times is kind of a significant feature. Anyway, if you're using itertools anyway, I'd do `lengths = chain(repeat(q + 1, r), repeat(q, 3 - r))` with `q` and `r` as in Pychopath's answer. – Kelly Bundy Apr 04 '22 at 10:26
  • @KellyBundy ah yeah that'd be more performant for sure! It's a little less obvious what's happening though, at least to me. I don't want to think about the math that hard -- treat it like dealing cards and "add one" in a cycle until we're done and that's perfect for me. – Adam Smith Apr 04 '22 at 10:41
  • Could even [remove your second loop](https://tio.run/##TU/NasMwDL77KXS0aQbrehmDvUJfobiJ0ggc2ZXVQZ8@k5PsRyeh71flqVPm03uRZRklz0CKojmnCjSXLAr9FIk7ECwYtQOqiXp0bsARakmkl0RVfaqGncOHA5u70eETBvqa8@ATcoNDw1fYDjedqjFWb79Z@zsc4GjK8BPmzecML3bZdIL6EIY5Fr@1@CW20muGaXf34NyYBRiIQSLf0L@97vX4eulTrBVbhbX9RrBwPhz3sCLE9lZDW2Bbuv8P/5l0cAo2y/IN) as well. Btw, actually the question asks for N sublists, with N=3 only being an example. And it already did when you posted your answer, their edit beating you by two seconds :-P – Kelly Bundy Apr 04 '22 at 10:43
  • @KellyBundy haha now we're getting into code golf territory. This is where I expect the author to walk me through their logic in the code review :). I did edit to allow for `n` sublists, given the two second skew ;) – Adam Smith Apr 04 '22 at 17:33
  • Nah, not code golf. Speed. Trying to move everyting into C code. The `[0 for _ in range(n)]` btw really makes it look like you don't understand mutable and immutable. I know you do, I'm just telling you that's what it looks like. – Kelly Bundy Apr 04 '22 at 17:55
  • @KellyBundy I'll run that risk ;) – Adam Smith Apr 04 '22 at 18:27
1

So, I hardly tried to make it in one line. Here is what I ended up with

import math

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
N = 3

lists = [nb_classes[math.ceil(i): math.ceil(i + len(nb_classes) / N)] for i in (len(nb_classes) / N * j for j in range(N))]

print(lists)  # [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]

But this may be a little bit complicated, you just need to ceil your indexes

sudden_appearance
  • 1,968
  • 1
  • 4
  • 15
1

Divide to get quotient and remainder. Normal sublist length is q, but the first r sublists get an extra element:

def divide(lst, N):
    q, r = divmod(len(lst), N)
    i = 0
    return [lst[i : (i := i + q + (j < r))]
            for j in range(N)]
Pychopath
  • 1,560
  • 1
  • 8
0

This is my solution:

def splitlist(mylist: list,n: int):
    lst = mylist
    res_list = []
    while lst:
        if len(lst) < n:
            # Not sure if the ValueError is approptiate
            raise ValueError()
        if len(lst) % n == 0:
            slice_at_idx = n

        else:
            # next slice will be n+1 size (4 in your case)
            slice_at_idx = n+1
        res_list.append(lst[:(slice_at_idx)])
        lst = lst[(slice_at_idx):]
    
    return res_list
            


nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9]
N = 3

print(splitlist(nb_classes,N))

nb_classes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

# if you know the resulting amount of lists
lst_1,lst_2,lst_3 = splitlist(nb_classes,N)

Sandwichnick
  • 1,379
  • 6
  • 13