Python grouping items 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 tidier solution for grouping list items 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 
+7
python list
source share
6 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 will work with any iterable, including endless iterations:

 >>> 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 can do this with one liner with a little trick to "embed" the iter() call in your list object:

 list([next(it) for _ in _range(s)] for it in (iter(my_list),) for s in count(1)) 
+13
source share

I don’t quite understand why you want to do this, which I’m talking about only because there is probably a specific way to answer your question, but I would say 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 this point you can get it via list(increasing_groups(some_list)) .

+1
source share

Yes, there is a simple answer.

 >>> test = [1, 2, 3, 4, 5, 6, 7] >>> bound = int((-1 + (1 + 8 * len(test)) ** 0.5) / 2) >>> res = [test[(i + 1) * i // 2 : (i + 1) * (i + 2) // 2] for i in xrange(bound)] >>> res [[1], [2, 3], [4, 5, 6]] 

Because the size of each slice is an arithmetic sequence. And the equation for calculating the total number of arithmetic sequences is known. Thus, we could simply calculate the start and end index of each slice directly with this equation.

+1
source share

You can track the number of items per slice using itertools.count , and you can select items using 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]] 

Important

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

this line ensures that the last item in the list remains only if its length is greater than the last than one.

+1
source share
 def incr_grouped(iterable): it, n = iter(iterable), 1 while True: yield [next(it) for _ in range(n)] n += 1 

The key point here is that the StopIteration next(it) exception also breaks the while . This means that you can lose the last items that are not installed in the group.

 >>> list(incr_grouped('ABCDEF')) [['A'], ['B', 'C'], ['D', 'E', 'F']] >>> list(incr_grouped([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])) [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]] 

It can be made even more compact using itertools . Mark Martijn Pieters answer.

+1
source share

it

 (n * (n - 1) / 2, n * (n + 1) / 2) 

Gives you, according to Gaus, the start and end indices of the nth element of your new list.

therefore

 my_list[n * (n - 1) / 2 : n * (n + 1) / 2] 

It is the nth element of the list and with a little dumb filtering:

 my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] [my_list[n * (n - 1) / 2: n * (n + 1)/ 2] for n in range(1, len(my_list)) if n * (n + 1)/ 2 <= len(my_list)] # [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]] 

A proper loop with an actual break is likely to be better though

Edit

Now that I know how StopIteration caught by list (thanks Martjin), a simple close condition can be met with:

 list(my_list[n * (n - 1) // 2: n * (n + 1) // 2] for n in count(1) if iter(my_list[n * (n + 1)/ 2:]).next() > -1) 

If -1 less than any item in your list. (And gender division for integer input in python 3.)

+1
source share

All Articles