trouble appending lists to another list - python

I have this code which takes a list of numbers and groups together all those that add up to 21. My problem is that in the end I want the numbers to all be lists in a list, but I am having trouble achieving that. Any advise would be appreciated
def twentyone(seq, groups = []):
goal = 21
s = sum(groups)
final = []
if s == goal:
final.append(groups)
print (final)
if s >= goal:
return
for i in range(len(seq)):
n = seq[i]
remaining = seq[i+1:]
twentyone(remaining, groups + [n])
#
seq = [1, 5, 6, 7, 10, 2, 11]
(twentyone(seq))
current output is:
[[1, 5, 6, 7, 2]]
[[1, 7, 2, 11]]
[[5, 6, 10]]
[[10, 11]]
I want the output to be:
[[1, 5, 6, 7, 2], [1, 7, 2, 11], [5, 6, 10], [10, 11]]

You are creating new final list each time when it recursively calls itself. You just have to pass it as a default argument.
def twentyone(seq, groups = [], final = []): #default final list
goal = 21
s = sum(groups)
if s == goal:
final.append(groups)
if s >= goal:
return
for i in range(len(seq)):
n = seq[i]
remaining = seq[i+1:]
twentyone(remaining, groups + [n])
return final
seq = [1, 5, 6, 7, 10, 2, 11]
print twentyone(seq)
Results:-
[[1, 5, 6, 7, 2], [1, 7, 2, 11], [5, 6, 10], [10, 11]]
But the above solution will cause the final list grow each time twentyone function will be called. So we can create a new final list only for the first time it is called using first_call flag as follows:
def twentyone(seq, groups = None, final = None, first_call=True):
if not groups:
groups = []
if first_call:
final = []
goal = 21
s = sum(groups)
if s == goal:
final.append(groups)
if s >= goal:
return
for i in range(len(seq)):
n = seq[i]
remaining = seq[i+1:]
twentyone(remaining, groups + [n], final, False)
return final
seq = [1, 5, 6, 7, 10, 2, 11]
print twentyone(seq)
print twentyone(seq)
Yields:
[[1, 5, 6, 7, 2], [1, 7, 2, 11], [5, 6, 10], [10, 11]]
[[1, 5, 6, 7, 2], [1, 7, 2, 11], [5, 6, 10], [10, 11]]

extending on Tanveers answer (which is the best approach in my opinion), you could also move the final variable outside or even use a static variable. The main problem in your code is that you are creating new final variable in each recursive call. These code below fixes that.
Local variable approach:
final = []
def twentyone(seq, groups = []):
goal = 21
s = sum(groups)
if s == goal:
final.append(groups)
if s >= goal:
return
for i in range(len(seq)):
n = seq[i]
remaining = seq[i+1:]
twentyone(remaining, groups + [n])
seq = [1, 5, 6, 7, 10, 2, 11]
twentyone(seq)
print (final)
Static variable approach:
class myfinal:
final=[]
def twentyone(seq, groups = []):
goal = 21
s = sum(groups)
if s == goal:
myfinal.final.append(groups)
if s >= goal:
return
for i in range(len(seq)):
n = seq[i]
remaining = seq[i+1:]
twentyone(remaining, groups + [n])
seq = [1, 5, 6, 7, 10, 2, 11]
twentyone(seq)
print (myfinal.final)

Related

How to divide a list or array into the correct number of groups

I have tried two methods of creating groups of numbers and then dividing those groups into smaller groups of sequential numbers and selecting one.
The first method used lists, but they wouldn't divide the groups correctly.
# This program prints the wrong number of elements.
# I need the correct number of elements, and I want the list to
# deal with floats.
begin = 1
end = 22
num_groop = 2
num_in_groop = (begin + end) // num_groop
lis = []
# loop iterates through index making list from beginning to end
end = num_in_groop
for _ in np.arange(num_groop):
lis.append(list(np.arange(begin, end+1)))
begin += num_in_groop
end += num_in_groop
print('lis', lis,)
# a function to choose one group from the lis and print it
x_1 = lis[0]
x_2 = lis[1]
inp = input('Choose group 1 or 2 by entering 1 or 2\n')
intinp = int(inp)
def choosefunc():
if intinp == 1:
del x_2[:]
print('You chose group x_1 = ',x_1[:])
elif intinp == 2:
del x_1[:]
print('You chose group x_2 = ',x_2[:])
choosefunc()
print('lis is now', lis)
The problem with this is that when it's repeated to narrow down the groups, it divides only using integers. Though the original max number was 22, after repeating this twice, it produces the wrong number of lists. To be correct maths, it should be this:
The first division of the list into an even number is fine:
[[1,2,3,4,5,6,7,8,9,10,11], [12,13,14,15,16,17,18,19,20,21,22]].
Then when choosing one of these groups, choose the first, and divide by two again that's where the maths doesn't work. It should be this:
lis [[1, 2, 3, 4, 5, 5.5], [5.6, 7, 8, 9, 10, 11]]
But because it doesn't seem to handle floats, it is this:
lis [[1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12]].
That's not correct maths. 22/2 = 11, and 11/2 = 5.5.
I want the lists to be of equal size.
# ---------------------------------------------------
When I try to solve the problem using lists, by using numpy arrays, I get an error that stops me from continuing.
# I tried to solve this problem using array but getting an error.
# TypeError: 'tuple' object cannot be interpreted as an integer.
import numpy as np
begin = 1
end = 22
num_groop = 2
num_in_groop = (begin + end) // num_groop
lis = np.array([])
print('lis is now', lis) # prints the new value for lis
end = num_in_groop
for _ in np.arange(num_groop):
print('line20, lis now', lis)
lis(np.arange(range((begin, end+1)))) #error
begin += num_in_groop
end += num_in_groop
print('lis', lis)
If [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]] is an acceptable split, then
def subdivide_list(a):
midpoint = len(a) // 2
return a[:midpoint], a[midpoint:]
lists = [list(range(1, 12))]
for x in range(3):
print(x, lists)
new_lists = []
for a in lists:
new_lists.extend(subdivide_list(a))
lists = new_lists
does what you want:
0 [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]]
1 [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]]
2 [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
EDIT
This also works for [list(range(1, 23))] (print adjusted to show the lengths of the lists):
0 [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]] [22]
1 [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]] [11, 11]
2 [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19, 20, 21, 22]] [5, 6, 5, 6]

Python islice class is not working as expected

I am writing a function that divides a list into (almost) equal 'n' distributions. I want this function to return a generator, but there appears to be an issue with yielding a generator. The function works just fine with iterables. Take a look at this snippet:
import itertools
def divide_list(array, n, gen_length=None):
"""
:param array: some iterable that you wish to divide
:param n: the number of lists you would like to return
:param gen_length: The length of the generator if array is a generator. Not necessary for lists and tuples.
:return: a generator of the divided list
Example:
In: list(divide_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
Out: [[1, 2, 3], [4, 5], [6, 7], [8, 9]]
"""
if isinstance(array, (list, tuple)):
floor, rem = divmod(len(array), n)
items_index = (0, floor)
for _ in range(n):
prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
yield array[prev:next_]
items_index = (next_, next_ + floor)
rem -= 1
else:
floor, rem = divmod(gen_length, n)
items_index = (0, floor)
for _ in range(n):
prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
yield itertools.islice(array, prev, next_)
items_index = (next_, next_ + floor)
rem -= 1
if __name__ == '__main__':
array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
print('Generator:')
print('----------')
for value in divide_list(array_, 3, gen_length=8):
print(list(value))
print('')
array_ = [12, 7, 9, 31, 13, 11, 7, 3]
print('List:')
print('-----')
for value in divide_list(array_, 3):
print(value)
Here is the ouput:
Generator:
----------
[12, 7, 9]
[7, 3]
[]
List:
-----
[12, 7, 9]
[31, 13, 11]
[7, 3]
Why is the last generator exhausted? Sometimes, it exhausts the last two generators.
The explanation as to why this isn't working is because you are using islice to skip elements when you provided it a non-zero starting point. The key issue here is that you are supposed to advance the iterator by an amount, not skipping any at each yield. This is different than the sequence case, where you give it explicit indices for each case.
However, note, you don't need to handle these cases differently. Here's a super simple approach that handles both cases - the key is to always use an iterator:
def divide(iterable, n, length=None):
if length is None:
length = len(iterable)
it = iter(iterable)
floor, rem = divmod(length, n)
while result := list(islice(it, floor + bool(rem))):
yield result
rem = max(rem - 1, 0)
In the REPL:
>>> from itertools import islice
>>> def divide(iterable, n, length=None):
... if length is None:
... length = len(iterable)
... it = iter(iterable)
... floor, rem = divmod(length, n)
... while result := list(islice(it, floor + bool(rem))):
... yield result
... rem = max(rem - 1, 0)
...
>>> list(divide([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
[[1, 2, 3], [4, 5], [6, 7], [8, 9]]
>>> list(divide(iter([1, 2, 3, 4, 5, 6, 7, 8, 9]), 4, length=9))
[[1, 2, 3], [4, 5], [6, 7], [8, 9]]
the problem is you don't take into account that you already consumed the iterator
>>> import itertools
>>> array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
>>> list(itertools.islice(array_,0,3))
[12, 7, 9]
>>> list(itertools.islice(array_,3,6)) #where are 31,13 and 11? you skipped them bacause, see below
[7, 3]
>>> array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
>>> list(itertools.islice(array_,0,3))
[12, 7, 9]
>>> list(array_) #this is what remains in the iterator
[31, 13, 11, 7, 3]
>>>
Thank's to #KellyBundy, I was able to modify the code to get the expected results. I was not aware that islice was cutting the generator then shifting those values back to the beginning at index 0. I was treating it as if it left a null value at the indices that I cut off. Here is the modified code:
import itertools
def divide_list(array, n, gen_length=None):
"""
:param array: some iterable that you wish to divide
:param n: the number of lists you would like to return
:param gen_length: The length of the generator if array is a generator. Not necessary for lists and tuples.
:return: a generator of the divided list
Example:
In: list(divide_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 4))
Out: [[1, 2, 3], [4, 5], [6, 7], [8, 9]]
"""
if isinstance(array, (list, tuple)):
floor, rem = divmod(len(array), n)
items_index = (0, floor)
for _ in range(n):
prev, next_ = items_index[0], items_index[1] + 1 if rem > 0 else items_index[1]
yield array[prev:next_]
items_index = (next_, next_ + floor)
rem -= 1
else:
floor, rem = divmod(gen_length, n)
up_to = floor
for _ in range(n):
up_to = up_to + 1 if rem > 0 else up_to
yield itertools.islice(array, up_to)
up_to = floor
rem -= 1
if __name__ == '__main__':
array_ = iter([12, 7, 9, 31, 13, 11, 7, 3])
print('Generator:')
print('----------')
for value in divide_list(array_, 3, gen_length=8):
print(list(value))
print('')
array_ = [12, 7, 9, 31, 13, 11, 7, 3]
print('List:')
print('-----')
for value in divide_list(array_, 3):
print(value)
This will output the expected result:
Generator:
----------
[12, 7, 9]
[31, 13, 11]
[7, 3]
List:
-----
[12, 7, 9]
[31, 13, 11]
[7, 3]

reverse ascending sequences in a list

Trying to figure out how to reverse multiple ascending sequences in a list.
For instance: input = [1,2,2,3] to output = [2,1,3,2].
I have used mylist.reverse() but of course it reverses to [3,2,2,1]. Not sure which approach to take?
Example in detail:
So lets say [5, 7, 10, 2, 7, 8, 1, 3] is the input - the output should be [10,7,5,8,7,2,3,1]. In this example the first 3 elements 5,7,10 are in ascending order, 2,7,8 is likewise in ascending order and 1,3 also in ascending order. The function should be able to recognize this pattern and reverse each sequence and return a new list.
All you need is to find all non-descreasing subsequences and reverse them:
In [47]: l = [5, 7, 10, 2, 7, 8, 1, 3]
In [48]: res = []
In [49]: start_idx = 0
In [50]: for idx in range(max(len(l) - 1, 0)):
...: if l[idx] >= l[idx - 1]:
...: continue
...: step = l[start_idx:idx]
...: step.reverse()
...: res.extend(step)
...: start_idx = idx
...:
In [51]: step = l[start_idx:]
In [52]: step.reverse()
In [53]: res.extend(step)
In [54]: print(res)
[10, 7, 5, 8, 7, 2, 3, 1]
For increasing subsequences you need to change if l[idx] >= l[idx - 1] to if l[idx] > l[idx - 1]
Walk the list making a bigger and bigger window from x to y positions. When you find a place where the next number is not ascending, or reach the end, reverse-slice the window you just covered and add it to the end of an output list:
data = [5, 7, 10, 2, 7, 8, 1, 3]
output = []
x = None
for y in range(len(data)):
if y == len(data) - 1 or data[y] >= data[y+1]:
output.extend(data[y:x:-1])
x = y
print(output)
There is probably a more elegant way to do this, but one approach would be to use itertools.zip_longest along with enumerate to iterate over sequential element pairs in your list and keep track of each index where the sequence is no longer ascending or the list is exhausted in order to slice, reverse, and extend your output list with the sliced items.
from itertools import zip_longest
d = [5, 7, 10, 2, 7, 8, 1, 3]
results = []
stop = None
for i, (a, b) in enumerate(zip_longest(d, d[1:])):
if not b or b <= a:
results.extend(d[i:stop:-1])
stop = i
print(results)
# [10, 7, 5, 8, 7, 2, 3, 1]
data = [5, 7, 10, 2, 7, 8, 1, 3,2]
def func(data):
result =[]
temp =[]
data.append(data[-1])
for i in range(1,len(data)):
if data[i]>=data[i-1]:
temp.append(data[i-1])
else:
temp.append(data[i-1])
temp.reverse()
result.extend(temp)
temp=[]
if len(temp)!=0:
temp.reverse()
result.extend(temp)
temp.clear()
return result
print(func(data))
# output [10, 7, 5, 8, 7, 2, 3, 1, 2]
You could define a general handy method which returns slices of an array based on condition (predicate).
def slice_when(predicate, iterable):
i, x, size = 0, 0, len(iterable)
while i < size-1:
if predicate(iterable[i], iterable[i+1]):
yield iterable[x:i+1]
x = i + 1
i += 1
yield iterable[x:size]
Now, the slice has to be made when the next element is smaller then the previous, for example:
array = [5, 7, 10, 2, 7, 8, 1, 3]
slices = slice_when(lambda x,y: x > y, array)
print(list(slices))
#=> [[5, 7, 10], [2, 7, 8], [1, 3]]
So you can use it as simple as:
res = []
for e in slice_when(lambda x,y: x > y, array):
res.extend(e[::-1] )
res #=> [10, 7, 5, 8, 7, 2, 3, 1]

Reversing order in incrementing digits

I have a list of numbers, and I'm trying to do the following in a way as efficient as possible.
For each consecutively incrementing chunk in the list I have to reverse its order.
This is my attempt so far:
l = []
l_ = []
i = 0
while i <= len(a)-1:
if a[i] < a[i+1]:
l_= l_ + [a[i]]
else:
l = l_ + [a[i]]
l_ = []
i = i + 1
I'd appreciate any guidance or other approaches.
So, for the following list:
a = [1,5,7,3,2,5,4,45,1,5,10,12]
I would like to obtain:
[7,5,1,3,5,2,45,4,12,10,5,1]
Try this:
(with fixes from #Scott Boston and #myrmica)
nums = [1, 3, 5, 4, 6, 8, 9, 7, 2, 4] # sample input
chunk = [] # keep track of chunks
output = [] # output list
for i in nums:
if chunk and i < chunk[-1]:
output.extend(chunk[::-1]) # add reversed chunk to output
chunk[:] = [i] # clear chunk
else:
chunk.append(i) # add to chunk
output.extend(chunk[::-1]) # empty leftover chunk
print(output)
with comprehension lists :
a = [1,5,7,3,2,5,4,45,1,5,10,12]
split=[0]+[i for i in range(1,len(a)) if a[i-1]>a[i]]+[len(a)]
#[0, 3, 4, 6, 8, 12]
chunks=[list(reversed(a[i:j])) for i,j in zip(split[:-1],split[1:])]
#[[7, 5, 1], [3], [5, 2], [45, 4], [12, 10, 5, 1]]
print(sum(chunks,[]))
#[7, 5, 1, 3, 5, 2, 45, 4, 12, 10, 5, 1]

Stretch lists to fit each others sizes

Say I have 3 mismatched sizes of lists, [3, 7, 6], [12, 67, 89, 98], and [1, 2, 3, 4, 5, 6, 7]
I want a function to do this:
>>> stretch([3, 7, 6], [12, 67, 89, 2], [1, 2, 3, 4, 5, 6, 7])
[3, 3, 3, 7, 7, 6, 6], [12, 67, 67, 89, 89, 2, 2], [1, 2, 3, 4, 5, 6, 7]
So, what I want is for all the smaller lists to be stretched to the length of the largest list. If there is 2 largest equally sized lists leave them the same length. I tried using one function, but it only worked using ranges. I would like it to work with everything. Here it is for reference:
import numpy
def zipstretch(*args):
range_tups = [(x[0], x[-1]) for x in args]
shifts = [x[0] for x in range_tups]
range_tups = [(x[0]-y, x[1]-y, n) for x, y in zip(range_tups, shifts)]
ranges = []
for x, s in zip(range_tups, shifts):
h = s
temp = list()
for y in range(max(range_tups, key=lambda z: len(z))[0], max(range_tups, key=lambda z: len(z))[1]):
temp.append(h)
h+=x[1]/max(range_tups, key=lambda z: len(z))[1]
ranges.append(numpy.array(temp))
return ranges
I came up with this:
def stretch(*lists):
length = max([len(l) for l in lists])
return [[l[i * len(l) // length] for i in range(length)]
for l in lists]
It computes the the target length as the maximum over all lists, and then stretches the list based on their index, similar to how you would implement naïve, one-dimensional image scaling.
Not super Pythonic, Florian's answer is much better, but I wanted to post the answer I came up with anyway.
def stretch(*lists):
max_len = max([len(l) for l in lists])
stretched = []
for l in lists:
init_factor = factor = (max_len - 1) / len(l)
new_l = []
i = 0
j = 1
while j <= max_len:
new_l.append(l[i])
if j > factor:
i += 1
factor += init_factor
j += 1
stretched.append(new_l)
return stretched
stretch([3, 7, 6], [12, 67, 89, 2], [1, 2, 3, 4, 5, 6, 7])
# => [[3, 3, 3, 7, 7, 6, 6], [12, 12, 67, 67, 89, 2, 2], [1, 2, 3, 4, 5, 6, 7]]

Categories