Merge two list with some order rule in python - python

I don't know how to give an accurate title, but here's the problem.
The problem:
I want to give a ranking list (imagine some top list) with some position preserved already.
Say I got 7 slots [1, 2, 3, 4, 5, 6, 7, 8] and some has already preserved postion 1, 3, 4, 7, 9. (As we only have 8 slots, the perserved postion 9 will means the last slot.)
Then I have 2, 5, 6 slots left, which I have to fill them with other objects.
The simplified question:
I have two list:
>>> a = [1, 3, 4, 7, 9]
>>> b = [object_x, object_y, object_z]
And I want to merge them to this:
>>> c = [1, object_x, 3, 4, object_y, object_z, 7, 9]
(We can take the 'object_x' here as 0.)
That's it, just want to see if there is an elegant way to implement this.
(Edit whole question based on the comments. Thank you guys very much.)

You could use a generator:
def merge(a, b):
b_clone = b[:]
for n in range(min(a), max(a) + 1):
if n in a:
yield n
elif b_clone:
yield b_clone.pop(0)

I believe this covers the edge cases, I do, however, agree with others that it seems there must be a better way of doing this. It might be worth explaining the context of what you are trying to do. There is probably a way to do it without all of this.
def merge(a, b):
b = list(b)
a = iter(a)
current = 1
for item in a:
while item != current:
if b:
yield b.pop(0)
else:
yield item
yield from a # <3.3 use `for item in a: yield item` instead.
return
current += 1
yield item
current += 1
Which appears to work as per your spec:
>>> print(list(merge([1, 3, 4, 7, 9], [0, 0, 0])))
[1, 0, 3, 4, 0, 0, 7, 9]
>>> print(list(merge([2, 4, 5], [1, 3])))
[1, 2, 3, 4, 5]
It's also unclear what should happen given extra elements in b - this ignores them, but adding yield from b (or, <3.3 for item in b: yield item) to the end would give them as the final elements.

This should also work:
>>>a = [2, 3, 5, 6, 7, 9]
>>>b = [0, 0, 0]
>>>length = len(a)
>>>i = 0
>>>while (i < length):
if a[i] != i+1:
a.insert(i, b.pop(0))
length += 1
i += 1
>>>print(a)
[0, 2, 3, 0, 5, 6, 7, 0, 9]

Thanks all, and I get this solution inspired by #jurgenreza
>>> a = [1, 3, 4, 7, 9]
>>> b = [0, 0, 0]
>>> for i in a:
b.insert(i - 1, i)
>>> print b
[1, 0, 3, 4, 0, 0, 7, 9]

You would possibly be better off avoiding having to do this merging in the first place. If you know in advance how many possible ranks there are, you can start your list off as containing that many Nones, and every time you occupy a space,set that using list item assignment rather than appending. Then your final merge is as simple as:
def merge(a, b):
b = iter(b)
for i,x in a:
if x is None:
a[i] = next(b)
If you were so inclined, you could put this whoe data structure into a class, which would also allow you to, for example, check when you're trying to overwrite an occupied position (if that would be an error):
class Ranks:
def __init__(self, size):
self._list = [None] * size
def __getitem__(self, position):
return self._list[position]
def __setitem__(self, position, val):
if self._list[position] is None:
raise ValueError('attempting to clobber existing rank data')
self._list[position] = val

Related

Is there a consistent expression for python list reverse selection?

The case is if I want to reverse select a python list to n like:
n = 3
l = [1,2,3,4,5,6]
s = l[5:n:-1] # s is [6, 5]
OK, it works, but how can I set n's value to select the whole list?
let's see this example, what I expect the first line is [5, 4, 3, 2, 1]
[40]: for i in range(-1, 5):
...: print(l[4:i:-1])
...:
[]
[5, 4, 3, 2]
[5, 4, 3]
[5, 4]
[5]
[]
if the upper bound n set to 0, the result will lost 0. but if n is -1, the result is empty because -1 means "the last one".
The only way I can do is:
if n < 0:
s = l[5::-1]
else:
s = l[5:n:-1]
a bit confusing.
To fully reverse the list by slicing:
l = [1,2,3,4,5,6]
print(l[::-1])
#[6, 5, 4, 3, 2, 1]
If you want to be able to partially or fully reverse the list based on the value of n, you can do it like this:
l = [1,2,3,4,5,6]
def custom_reverse(l,n):
return [l[i] for i in range(len(l)-1,n,-1)]
print(custom_reverse(l,3)) #[6, 5]
print(custom_reverse(l,-1)) #[6, 5, 4, 3, 2, 1]
Hopefully this is what you mean.
print(l[n+1::-1])

How to deal with this exercise using recursion in Python?

I am struggling to solve an exercise regarding lists in Python. The exercise says:
Write a function that takes a list containing numbers and lists of
numbers inside them, reverses it (including the lists of numbers
inside the main list) using recursion.
Afterwards, the function should return a tuple containing three
elements. The first element represents the amount of all the numbers
present in the list (including the ones in the sublists), the second
element is the sum of all the numbers (also the ones inside the
sublists), and the third element is a sorted list of all the integers.
Again we should use recursion.
I managed to reverse the whole list using two functions, but when it comes to accessing the numbers inside the sublists to use them for the tuple, I really don't know what to do.
Here you can have a look at the list of lists:
lista = [3, 3, 5, [[1, 8, [9, 3]], 3, [2, [9, [5, 6],[9]] ] ]]
Here you can check my code:
def exercise (lista):
lista_ordinata = []
count = 0
somma = 0
reverse_list(lista)
for x,y in enumerate(lista):
if isinstance (x,(int)):
count += 1
else:
count = 0
for num in lista:
if isinstance(num,(int)):
somma += num
for i in lista:
if isinstance(i,int):
lista_ordinata.append(i)
return (count,somma,lista_ordinata)
def is_list (lista):
return isinstance(lista,list)
def reverse_list(lista):
lista_nuova = lista[::-1]
for x,y in enumerate(lista_nuova):
if is_list(y):
lista_nuova[x] = reverse_list(y)
lista.clear()
lista.extend(lista_nuova)
return lista
Here you can see the expected list which I reversed:
lista = [[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
The function must return the following tuple:
(13,66, [1,2,3,5,6,8,9])
The output I get is incorrect:
(4, 11, [5, 3, 3])
The first element should be the counting of all the numbers, and not just the numbers outside the sublists, The sum is also incorrect, The list is not outputting all the numbers.
What should I do? Keep in mind that the "exercise" function should use recursion.
You are using recursion just fine in the reverse function, just use recursion in you exercise function:
def exercise(lista):
lista_ordinata = []
count = 0
somma = 0
# reverse_list(lista)
for x in lista:
if isinstance(x, list):
recursion = exercise(x) # call recursion until you have a list with only integers
# and add the result to your running totals
count += recursion[0]
somma += recursion[1]
lista_ordinata.extend(recursion[2])
else:
count += 1
somma += x
lista_ordinata.append(x)
lista_ordinata = sorted(list(set(lista_ordinata)))
return count, somma, lista_ordinata
print(exercise(lista))
(13, 66, [1, 2, 3, 5, 6, 8, 9])
def exercise(a, polish=sorted):
if not isinstance(a, list):
return 1, a, {a}
a.reverse()
amount, sum, numbers = 0, 0, set()
for b in a:
a, s, n = exercise(b, set)
amount += a
sum += s
numbers |= n
return amount, sum, polish(numbers)
Or with a little helper doing the reversals and collecting the numbers:
def exercise(a):
def go(a):
if isinstance(a, list):
a.reverse()
for b in a:
go(b)
else:
numbers.append(a)
numbers = []
go(a)
return len(numbers), sum(numbers), sorted(set(numbers))
Demo:
lista = [3, 3, 5, [[1, 8, [9, 3]], 3, [2, [9, [5, 6],[9]] ] ]]
>>> exercise(lista)
(13, 66, [1, 2, 3, 5, 6, 8, 9])
>>> lista
[[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
Here's a way without any loops at all.
First of all, we can have a definition for a partial insertion sort, which inserts any value to its correct position in an existing sorted array.
def partialInsertionSort(val, idx, sortarr):
if idx == len(sortarr):
sortarr.append(val)
elif val < sortarr[idx]:
sortarr.insert(idx, val)
elif val > sortarr[idx]:
sortarr = partialInsertionSort(val, idx+1, sortarr)
return sortarr
As you travel through any list a, unless you've reached the end of a, there are two possibilities:
The current item is another list -> Recurse through the sublist!
The current item is a digit -> Increment total and count. If this digit is a previously unseen digit, 'insert' it to your list of sorted digits.
Done with the above two possible options, you have thus processed the current element, and can move to the next element by incrementing the index i.
def reverse_and_sort(a, i, out, sortarr, total, count):
if i==len(a):
return out, sortarr, total, count
if isinstance(a[i], list):
a[i], sortarr, total, count = reverse_and_sort(a[i], 0, [], sortarr, total, count)
else:
total += a[i]
count += 1
if a[i] not in sortarr:
sortarr = partialInsertionSort(a[i], 0, sortarr)
out.insert(0, a[i])
return reverse_and_sort(a, i+1, out, sortarr, total, count)
Test:
rev, srt, total, count = reverse_and_sort(lista, 0, [], [], 0, 0)
print(rev) #[[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
print(srt) #[1, 2, 3, 5, 6, 8, 9]
print(total) #66
print(count) #13
A relatively short version, which uses the add operator is given by that:
from operator import add
def sorter(l):
if isinstance(l, list):
temp = [0, 0, []]
for e in map(sorter, l):
temp = list(map(add, e, temp))
return temp[0], temp[1], sorted(set(temp[2]))
return 1, l, [l]
It should give the correct result:
test_list = [[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
sorter(test_list)
>>> (13, 66, [1, 2, 3, 5, 6, 8, 9])
Two liner approach!
Here is a simple solution to the problem, using indirect recursion. Indirect recursion is where a function f(x) calls a function g(x) which in turn calls f(x).
If your problem was just the first part, the solution is just a simple 2 liner -
f = lambda x: [g(i) for i in reversed(x)]
g = lambda x: f(x) if type(x)==list else x
f(lista)
[[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
Beautiful right?
The main idea here is that at an element level you just need to check if the element is an integer and return it, else you need to use another function that lets you iterate over the list. You can have counters at the level of the second function which lets you track the sum and the count and return it with the final function call.
Since you also have the 2nd part to the solution which is using a few counters, you can modify the above code for g(x) to incorporate that as below -
cnt = []
sm = []
#function to iterate over a reversed list
f = lambda x: [g(i) for i in reversed(x)]
#function to call f if list else updated counters and return element
def g(x):
if type(x)==list:
return f(x)
else:
cnt.append(1) #ONLY MODIFICATION
sm.append(x) #ONLY MODIFICATION
return x
#call f and return sum of counters
def rec(l):
o = f(l)
return o, (sum(cnt), sum(sm), sorted(sm))
out, tup = rec(lista)
print(out)
print(tup)
[[[[[9], [6, 5], 9], 2], 3, [[3, 9], 8, 1]], 5, 3, 3]
(13, 66, [1, 2, 3, 3, 3, 3, 5, 5, 6, 8, 9, 9, 9])
def exercise(lista, total = 0, count = 0, all_int = []):
for item in lista:
if isinstance(item, list):
count, total, all_int = exercise(item, total, count, all_int)
elif isinstance(item, int):
total += item
count += 1
if item not in all_int:
all_int.append(item)
all_int.sort()
lista.reverse()
return count, total, all_int
Maybe this would help :)
def exercise (lista):
reverse_list(lista)
r = count_numbers(lista), sum_numbers(lista), sorted(pull_out(lista))
return r
def is_list (lista):
return isinstance(lista,list)
def reverse_list(lista):
lista_nuova = []
for e in lista[::-1]:
if is_list(e):
lista_nuova.append(reverse_list(e))
else:
lista_nuova.append(e)
return lista_nuova
def count_numbers(lista):
c = 0
for e in lista:
if is_list(e):
c += count_numbers(e)
else:
c+=1
return c
def sum_numbers(lista):
s = 0
for e in lista:
if is_list(e):
s += sum_numbers(e)
else:
s+=e
return s
def pull_out(lista):
b_lista = []
for e in lista:
if is_list(e):
b_lista.extend(pull_out(e))
else:
b_lista.append(e)
return b_lista
lista = [3, 3, 5, [[1, 8, [9, 3]], 3, [2, [9, [5, 6],[9]] ] ]]
r = exercise(lista)
print(r)
# (13, 66, [1, 2, 3, 3, 3, 3, 5, 5, 6, 8, 9, 9, 9])
I did it with separate functions for clarity.

Take elements from multiple lists

Given multiple lists like the ones shown:
a = [1, 2, 3]
b = [5, 6, 7, 8]
c = [9, 0, 1]
d = [2, 3, 4, 5, 6, 7]
...
I want to be able to combine them to take as many elements from the first list as I can before starting to take elements from the second list, so the result would be:
result = [1, 2, 3, 8, 6, 7]
Is there a particularly nice way to write this? I can't think of a really simple one without a for loop. Maybe a list comprehension with a clever zip.
Simple slicing and concatenation:
a + b[len(a):]
Or with more lists:
res = []
for lst in (a, b, c, d):
res += lst[len(res):]
# [1, 2, 3, 8, 6, 7]
With itertools.zip_longest() for Python 3, works on any number of input lists:
>>> from itertools import zip_longest
>>> [next(x for x in t if x is not None) for t in zip_longest(a,b,c,d)]
[1, 2, 3, 8, 6, 7]
The default fill value is None so take the first none None element in each tuple created with the zip_longest call (you can change the defaults and criteria if None is a valid data value)
With functools.reduce:
from functools import reduce
print(list(reduce(lambda a, b: a + b[len(a):], [a, b, c, d])))
This outputs:
[1, 2, 3, 8, 6, 7]

Is a python list a circular data structure?

i am confuse at some point in my python code, i am trying to build a program which return the cumulative sum, that is where is the ith element is the sun of first i+1 elements from the origional list. So cunmulative sum of [1,2,3] is [1,3,6], i tried to build program and its working but for first element its adding last element as previous element, that made me think that is structure of python list something circle??
h=[]
a=[1,2,3,4,5,6]
for i in range(len(a)):
d=a[i]+(a[i-1]+1)
h.append(d)
print(h)
Result
[8, 4, 6, 8, 10, 12]
The culprit is with a[i - 1] in the 0th iteration:
d = a[i] + (a[i - 1] + 1)
What actually happens is that i - 1 is reduced to -1, and in python, a[-1] refers to the last element in the list:
In [568]: l = [1, 2, 3, 4]
In [569]: l[-1]
Out[569]: 4
The solution here would be to start your loop from 1. Alternatively, you would consider the use of a temp variable, as mentioned here:
a = [1, 2, 3, 4, 5, 6]
h = []
cumsum = 0
for i in a:
cumsum += i
h.append(cumsum)
print(h)
[1, 3, 6, 10, 15, 21]
As a side, if you're using numpy, this is as simple as a single function call with np.cumsum:
h = np.cumsum([1, 2, 3, 4, 5, 6])
print(h)
array([ 1, 3, 6, 10, 15, 21])
Just wanted to add that you could use itertools.accumulate to accumulate the results of a binary operation, in this case, addition. Note, itertools.accumulate actually defaults to addition:
>>> a = [1, 2, 3, 4, 5, 6]
>>> a = [1, 2, 3, 4, 5, 6]
>>> import itertools
>>> list(itertools.accumulate(a))
[1, 3, 6, 10, 15, 21]
But you could pass it a binary operation and do a cumulative product, for example:
>>> list(itertools.accumulate(a ,lambda x, y: x*y))
Better yet, harness the power of the operator module:
>>> list(itertools.accumulate(a, operator.add)) #cumulative sum
[1, 3, 6, 10, 15, 21]
>>> list(itertools.accumulate(a, operator.mul)) #cumulative product
[1, 2, 6, 24, 120, 720]
>>> list(itertools.accumulate(a, operator.truediv)) #cumulative quotient
[1, 0.5, 0.16666666666666666, 0.041666666666666664, 0.008333333333333333, 0.001388888888888889]
>>> list(itertools.accumulate(a, operator.floordiv)) #cumulative floor div
[1, 0, 0, 0, 0, 0]
Yes - it is a little like that. In python a[-1] gives you the last element of the list.
If you seed the result with the first item your code and start from 1 rather than 0 can be made to work:
a=[1,2,3,4,5,6]
h=[a[0]]
for i in range(1,len(a)):
d=a[i]+(h[i-1])
h.append(d)
print(h)
Lists are not circular, but the can be referenced from either the beginning or the end. You can use [-1] to reference from the end of the list.
But, if you try to reference the 20th element of a list that only has 10 elements, you will receive an error. If lists were circular, it would refer to the 10th element, but it does not.
What you want is this, I believe:
a = [1,2,3,4,5,6]
h = []
index = 0
for i in range(len(a) - 1):
first = a[index]
second = a[index + 1]
summation = first + second
h.append(summation)
index += 1
print(h)

Python list issue

I need some hints or an example, how can i localize in a list a the list b, then replace it with list c.
a=[1,3,6,2,6,7,3,4,5,6,6,7,8]
input the b list (this is the sublist the program searches for in list a).
b=[6,7]
when found return me the indexes were the sublist has been found and replace it each time with c=[0,0], so the result will be
[1,3,6,2,0,0,3,4,5,6,0,0,8]
Here's a more efficient approach than my first, using list-slicing:
>>> for i in xrange(len(a) - len(b) + 1):
... if a[i:i+len(b)] == b:
... a[i:i+len(b)] = c
...
>>> a
[1, 3, 6, 2, 0, 0, 3, 4, 5, 6, 0, 0, 8]
First attempt, for posterity....
If you don't need the intermediate indices, here's one approach, using string functions and taking a functional approach, not modifying your list in-place.
>>> a_as_str = ','.join(str(i) for i in a)
>>> print a_as_str
1,3,6,2,6,7,3,4,5,6,6,7,8
>>> b_as_str = ','.join(str(i) for i in b)
>>> b_as_str
'6,7'
>>> c_as_str = ','.join(str(i) for i in c)
>>> c_as_str
'0,0'
>>> replaced = a_as_str.replace(b_as_str, c_as_str)
>>> replaced
'1,3,6,2,0,0,3,4,5,6,0,0,8'
>>> [int(i) for i in replaced.split(',')]
[1, 3, 6, 2, 0, 0, 3, 4, 5, 6, 0, 0, 8]
This can be refactored as:
>>> def as_str(l):
... return ','.join(str(i) for i in l)
...
>>> def as_list_of_ints(s):
... return [int(i) for i in s.split(',')]
...
>>> as_list_of_ints(as_str(a).replace(as_str(b), as_str(c)))
[1, 3, 6, 2, 0, 0, 3, 4, 5, 6, 0, 0, 8]
you can do something similar to (written in python 3.2, use xrange in python 2.x):
for i in range(0, len(a)):
if a[i:i+len(b)] == b:
a[i:i+len(b)] = c
this will account for lists of all sizes.
This assumes list b == list c I don't know if that is what you want however, please state if it is not.
Output for lists:
a = [1,2,3,4,5,6,7,8,9,0]
b = [1,2]
c = [0,0]
Output:
[0, 0, 3, 4, 5, 6, 7, 8, 9, 0]
I give you an example
li=[1,3,6,2,6,7,3,4,5,6,6,7,8]
for i in range(len(li)):
if li[i:i + 2] == [3, 4]:
li[i:i + 2] = [0, 0]
I think that this code should work. If you want a more robust script I suggest you to check the occurrences of a substring in the original list an edit a copy (to avoid side-effect behaviors).
It is important also to consider what happens when the given pattern is created by the substitution.
I think this function should treat all cases as intended:
def replace(a, b, c):
ii = 0
while ii <= (len(a) - len(b) + 1):
print(ii)
if a[ii:ii+len(b)] == b:
a[ii:ii+len(b)] = c
ii += len(b)
else:
ii += 1
return a
The output using the original example:
[1, 3, 6, 2, 0, 0, 3, 4, 5, 6, 0, 0, 8]
Here is an example where the substitution creates the search pattern:
a = [1,1,1,1,1,1,1,1,1,6,6,7,7,1]
b = [6,7]
c = [0,6]
Output is as expected:
[1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 0, 6, 7, 1]
Any ideas on how to do this a bit more concisely?

Categories