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
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))
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))
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With