Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python grouping elements in a list in increasing size

my_list = [my_list[int((i**2 + i)/2):int((i**2 + 3*i + 3)/2)] for i in range(int((-1 + (1 + 8*len(my_list))**0.5)/2))]

Is there a neater solution to grouping the elements of a list into subgroups of increasing size than this?

Examples:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] --> [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
[1, 2, 3, 4] --> [[1], [2, 3]]
[1, 2, 3, 4, 5, 6] --> [[1], [2, 3], [4, 5, 6]]

EDIT

Here are the results from timeit:

from timeit import Timer
from itertools import count

def martijn(it):
    it = iter(it)
    return list([next(it) for _ in range(s)] for s in count(1))

def mathematical(it):
    upper_bound = int(((1 + 8*len(it))**0.5 + 1)//2)
    return [it[i*(i-1)//2:i*(i+1)//2] for i in range(1, upper_bound)]

def time(test, n):
    a = Timer(lambda: martijn(test)).timeit(n)
    b = Timer(lambda: mathematical(test)).timeit(n)
    return round(a, 3), round(b, 3)

>>> for i in range(8):
        loops = 10**max(0, (6-i))
        print(time([n for n in range(10**i)], loops), loops)
(6.753, 4.416) 1000000
(1.166, 0.629) 100000
(0.366, 0.123) 10000
(0.217, 0.036) 1000
(0.164, 0.017) 100
(0.157, 0.017) 10
(0.167, 0.021) 1
(1.749, 0.251) 1
>>> for i in range(8):
        loops = 10**max(0, (6-i))
        print(time(range(10**i), loops), loops)
(6.721, 4.779) 1000000
(1.184, 0.796) 100000
(0.367, 0.173) 10000
(0.218, 0.051) 1000
(0.202, 0.015) 100
(0.178, 0.005) 10
(0.207, 0.002) 1
(1.872, 0.005) 1
like image 953
Scorpion_God Avatar asked Apr 11 '14 14:04

Scorpion_God


3 Answers

Using a generator expression:

from itertools import count

try:
    _range = xrange
except NameError:
    # Python 3
    _range = range


def incremental_window(it):
    """Produce monotonically increasing windows on an iterable.

    Only complete windows are yielded, if the last elements do not form
    a complete window they are ignored.

    incremental_window('ABCDEF') -> ['A'], ['B', 'C'], ['D', 'E', 'F']
    incremental_window('ABCDE') -> ['A'], ['B', 'C']

    """
    it = iter(it)
    return ([next(it) for _ in _range(s)] for s in count(1))

Demo:

>>> list(incremental_window([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]))
[[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
>>> list(incremental_window([1, 2, 3, 4]))
[[1], [2, 3]]
>>> list(incremental_window([1, 2, 3, 4, 5, 6]))
[[1], [2, 3], [4, 5, 6]]

This is a generator that'll work with any iterable, including endless iterables:

>>> from itertools import count
>>> for window in incremental_window(count()):
...     print window
...     if 25 in window:
...         break
... 
[0]
[1, 2]
[3, 4, 5]
[6, 7, 8, 9]
[10, 11, 12, 13, 14]
[15, 16, 17, 18, 19, 20]
[21, 22, 23, 24, 25, 26, 27]

You could make that a one-liner with a little cheating to 'inline' the iter() call on your list object:

list([next(it) for _ in _range(s)] for it in (iter(my_list),) for s in count(1))
like image 126
Martijn Pieters Avatar answered Nov 04 '22 14:11

Martijn Pieters


I'm not honestly totally clear why you want to do this, which I mention purely because there's likely a task-specific way to answer your question, but I would argue that the following is at least clearer:

def increasing_groups(l):
    current_size = 1
    while l:
        yield l[:current_size]
        l = l[current_size:]
        current_size += 1

at which point you can get it via list(increasing_groups(some_list)).

like image 25
Benjamin Pollack Avatar answered Nov 04 '22 12:11

Benjamin Pollack


You can keep track of the number of items to slice with itertools.count and you can pick the items with itertools.islice.

# Initializations and declarations
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
from itertools import count, islice
counter, it = count(0), iter(data)

# Actual list construction
result = [[item] + list(islice(it, next(counter))) for item in it]

# Making sure that the last item of the list is consistent with the previous item
if len(result) > 1 and len(result[-1]) <= len(result[-2]): del result[-1]

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

The important thing is

if len(result) > 1 and len(result[-1]) <= len(result[-2]): del result[-1]

this line makes sure that, the last item in the list stays only if its length is greater than the last but one.

like image 27
thefourtheye Avatar answered Nov 04 '22 12:11

thefourtheye