Python slice command forgotten out of the loop - python

I want to build some lists (a, b, c, d) by extending periodically those given in input.
The structure of the periodicity is such that the first element of each list must not be repeated, while all the other elements must be until the maximum length set in input is reached (it is possible that the period does not get to be repeated an integer number of times).
To give an example, if I have as input
a = [1, 2, 3, 4] max_len=11
I want to obtain as output
a = [1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2]
I wrote this piece of code:
for mylist in [a, b, c d]:
period = mylist[1:] # all elements but the first are repeated
while len(mylist)< max_len:
mylist.extend(period)
mylist = mylist[:max_len] # cut to max_len
print mylist
print a, b, c, d
If I run this, I see from the two print commands that my lists are how I want them to be while the program is still in the loop, but they get back to the "non-sliced" length when out of the loop, that is, a gets back to the length greater than max_len where the period is repeated exactly 4 times:
a = [1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4]
Why is that? It looks like the program has forgotten the slicing (but not the extension).
I understand that it is a pretty simple problem, and I could get around it in some other way, but I would like to understand why this idea does not work.
Thank you for your help!

The problem is that mylist = mylist[:max_len] creates a new list instead of modifying mylist in place, so the results never propagate back to a et al.
If you replace:
mylist = mylist[:max_len]
with
mylist[:] = mylist[:max_len]
that'll fix it.
As an aside, you could use itertools for the transformation:
In [10]: [a[0]] + [v for v in itertools.islice(itertools.cycle(a[1:]), max_len - 1)]
Out[10]: [1, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2]

mylist in the loop starts off referring to one of the input lists such as a but when you assign to it you are just rebinding the name mylist to a new list, you are not modifying the original list.
This would work:
for mylist in [a, b, c, d]:
period = mylist[1:] # all elements but the first are repeated
while len(mylist)< max_len:
mylist.extend(period)
mylist[:] = mylist[:max_len] # cut to max_len
print mylist
print a, b, c, d
The slice assignment updates the original list.
Or you could try this, just updating the original list once:
for mylist in [a, b, c, d]:
mylist[1:] = (mylist[1:] * (1+max_len//(len(mylist)-1)))[:max_len]
print(mylist)
print a, b, c, d

Related

How to compare lists in python in subgroups

I'm new in python so any help or recomendation is appreciated.
What I'm trying to do is, having two lists (not necessarily inverted).
For instance:
l1 = [1,2,3,4,5]
l2 = [5,4,3,2,1]
Comparing them to return the common values, but not as anyone would normally do, which in this case, the return will be all the elements of the list, because they are the same, just inverted.
What I'm trying to compare is, the same thing but like in stages, or semi portions of the list, and check if there is any coincidence until there, if it is, return that element, if not, keep looking in the next group.
For instance:
the first iteration, would check (having the lists previously defined:
l1 = [1]
l2 = [5]
#is there any coincidence until there? -> false (keep looking)
2nd iteration:
l1 = [1, 2]
l2 = [5, 4]
#is there any coincidence until there? -> false (keep looking)
3rd iteration:
l1 = [1, 2, 3]
l2 = [5, 4, 3]
#is there any coincidence until there? -> true (returns 3,
#which is the element where the coincidence was found, not necessarily
#the same index in both lists)
Having in mind that it will compare the last element from the first list with all from the second till that point, which in this case will be just the first from the second list, if no matches, keep trying with the element immediately preceding the last from the first list with all from the second, and so on, returning the first item that matches.
Another example to clarify:
l1 = [1,2,3,4,5]
l2 = [3,4,5,6,7]
And the output will be 3
A tricky one:
l1 = [1,2,3,4]
l2 = [2,1,4,5]
1st iteration
l1 = [1]
l2 = [2]
# No output
2nd iteration
l1 = [1,2]
l2 = [2,1]
# Output will be 2
Since that element was found in the second list too, and the item that I'm checking first is the last of the first list [1,2], and looking if it is also in the sencond list till that point [2,1].
All of this for needing to implementate the bidirectional search, but I'm finding myself currently stuck in this step as I'm not so used to the for loops and list handling yet.
you can compare the elements of the two lists in the same loop:
l1 = [1,2,3,4,5]
l2 = [5,4,3,2,1]
for i, j in zip(l1, l2):
if i == j:
print('true')
else:
print('false')
It looks like you're really asking: What is (the index of) the first element that l1 and l2 have in common at the same index?
The solution:
next((i, a) for i, (a, b) in enumerate(zip(l1, l2)) if a == b)
How this works:
zip(l1, l2) pairs up elements from l1 and l2, generating tuples
enumerate() gets those tuples, and keeps track of the index, i.e. (0, (1, 5), (1, (2, 4)), etc.
for i, (a, b) in .. generates those pairs of indices and value tuples
The if a == b ensures that only those indices and values where the values match are yielded
next() gets the next element from an iterable, you're interested in the first element that matches the condition, so that's what next() gets you here.
The working example:
l1 = [1, 2, 3, 4, 5]
l2 = [5, 4, 3, 2, 1]
i, v = next((i, a) for i, (a, b) in enumerate(zip(l1, l2)) if a == b)
print(f'index: {i}, value: {v}') # prints "index: 2, value: 3"
If you're not interested in the index, but just in the first value they have in common:
l1 = [1, 2, 3, 4, 5]
l2 = [5, 4, 3, 2, 1]
v = next(a for a, b in zip(l1, l2) if a == b)
print(v) # prints "3"
Edit: you commented and updated the question, and it's clear you don't want the first match at the same index between the lists, but rather the first common element in the heads of the lists.
(or, possibly the first element from the second list that is in the first list, which user #AndrejKesely provided an answer for - which you accepted, although it doesn't appear to answer the problem as described)
Here's a solution that gets the first match from the first part of each list, which seems to match what you describe as the problem:
l1 = [1, 2, 3, 4, 5]
l2 = [5, 2, 6, 7, 8]
v = next(next(iter(x)) for n in range(max(len(l1), len(l2))) if (x := set(l1[:n+1]) & set(l2[:n+1])))
print(v) # prints "2"
Note: the solution fails if there is no match at all, with a StopIteration. Using short-circuiting with any() that can be avoided:
x = None if not any((x := set(l1[:n+1]) & set(l2[:n+1])) for n in range(max(len(l1), len(l2)))) else next(iter(x))
print(x)
This solution has x == None if there is no match, and otherwise x will be the first match in the shortest heads of both lists, so:
l1 = [1, 2, 3, 4, 5]
l2 = [5, 2, 6, 7, 8] # result 2
l1 = [1, 2, 3, 4, 5]
l2 = [5, 6, 7, 8] # result 5
l1 = [1, 2, 3, 4, 5]
l2 = [6, 7, 8] # result None
Note that also:
l1 = [1, 2, 3]
l2 = [4, 3, 2] # result 2, not 3
Both 2 and 3 seem to be valid answers here, it's not clear from your description why 3 should be favoured over 2?
If you do need that element of the two possible answers that comes first in l2, the solution would be a bit more complicated still, since the sets are unordered by definition, so changing the order of l1 and l2 in the answer won't matter.
If you care about that order, this works:
x = None if not any(x := ((set(l1[:n//2+1+n%2]) & set(l2[:n//2+1]))) for n in range(max(len(l1), len(l2)) * 2)) else next(iter(x))
This also works for lists with different lengths, unlike the more readable answer by user #BenGrossmann. Note that they have some efficiency in reusing the constructed sets and adding one element at a time, which also allows them to remember the last element added to the set corresponding with the first list, which is why they also correctly favor 3 over 2 in [[1, 2, 3], [4, 3, 2]].
If the last answer is what you need, you should consider amending their answer (for example using zip_longest) to deal correctly with lists of different lengths, since it will be more efficient for longer lists, and is certainly more readable.
Taking the solution from #BenGrossman, but generalising it for any number of lists, with any number of elements, and favouring the ordering you specified:
from itertools import zip_longest
lists = [[1, 2, 3, 4, 5],
[6, 7, 8, 5, 4]]
sets = [set() for _ in range(len(lists))]
for xs in zip_longest(*lists):
for x, s in zip(xs, sets):
s.add(x)
if i := set.intersection(*sets):
v = sorted([(lists[0].index(x), x) for x in i])[-1][1]
break
else:
v = None
print(v)
This works as described for all the examples, as well as for lists of unequal length, and will favour the elements that are farthest back in the first list (and thus earlier in the others).
The following can be made more efficient, but does work.
lists = [[1,2,3,4,5], # input to the script
[5,4,3,2,1]]
sets = [set(), set()]
for a,b in zip(*lists):
sets[0].add(a)
sets[1].add(b)
if sets[0]&sets[1]:
print("first element in first overlap:")
print(a)
break
else:
print("no overlap")
This results in the output
first element in first overlap:
3
Using lists = [[5,7,6],[7,5,4]] instead results in
first element in first overlap:
7

*Python* Using function parameters to repeat an element of a list within a list

I have a question regarding list operations and function parameters within Python.
Lets say I have the function
def list_and_index_repeat(a,b,c)
Now lets say that our a parameter is our list
list_and_index_repeat([1,2,3,4],b,c)
and our b parameter is our index within the said list.
list_and_index_repeat([1,2,3,4],3,c)
where the interger 3 represents our third element 4
Now that we have our element 4 how I would I repeat this element within that original list a
Example:
list_and_index_repeat([1,2,3,4],3,2)
So I am taking the third element within the list and then repeating it by 2 so I am left with the final output:
final_output[1,2,3,4,4,4]
Thank you,
Try:
def list_and_index_repeat(a, b, c):
return a[:b] + [a[b], *[a[b]] * c] + a[b + 1 :]
out = list_and_index_repeat([1, 2, 3, 4], 3, 2)
print(out)
Prints:
[1, 2, 3, 4, 4, 4]
Make a slice of the list up to b + 1 then, append the value at a[b] c times to the new list and return it:
def list_and_index_repeat(a,b,c):
new_list = a[:b+1]
for _ in range(c):
new_list.append(a[b])
return new_list
print(list_and_index_repeat([1,2,3,4],3,2))
Output:
[1, 2, 3, 4, 4, 4]
you can simply use the list.extend() method to achieve this.
for example:
a = []
a.extend(2 * "c")
print(a)
It will return this
["c", "c"]

Pythonic way to append output of function to several lists

I have a question that I haven't quite found a good solution to. I'm looking for a better way to append function output to two or more lists, without using temp variables. Example below:
def f():
return 5,6
a,b = [], []
for i in range(10):
tmp_a, tmp_b = f()
a.append(tmp_a)
b.append(temp_b)
I've tried playing around with something like zip(*f()), but haven't quite found a solution that way.
Any way to remove those temp vars would be super helpful though, thanks!
Edit for additional info:
In this situation, the number of outputs from the function will always equal the number of lists that are being appended to. The main reason I'm looking to get rid of temps is for the case where there are maybe 8-10 function outputs, and having that many temp variables would get messy (though I don't really even like having two).
def f():
return 5,6
a,b = zip(*[f() for i in range(10)])
# this will create two tuples of elements 5 and 6 you can change
# them to list by type casting it like list(a), list(b)
First solution: we make a list of all results, then transpose it
def f(i):
return i, 2*i
# First make a list of all your results
l = [f(i) for i in range(5)]
# [(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)]
# then transpose it using zip
a, b = zip(*l)
print(list(a))
print(list(b))
# [0, 1, 2, 3, 4]
# [0, 2, 4, 6, 8]
Or, all in one line:
a, b = zip(*[f(i) for i in range(5)])
A different solution, building the lists at each iteration, so that you can use them while they're being built:
def f(i):
return 2*i, i**2, i**3
doubles = []
squares = []
cubes = []
results = [doubles, squares, cubes]
for i in range(1, 4):
list(map(lambda res, val: res.append(val), results, f(i)))
print(results)
# [[2], [1], [1]]
# [[2, 4], [1, 4], [1, 8]]
# [[2, 4, 6], [1, 4, 9], [1, 8, 27]]
print(cubes)
# [1, 8, 27]
Note about list(map(...)): in Python3, map returns a generator, so we must use it if we want the lambda to be executed.list does it.
For your specific case, the zip answers are great.
Using itertools.cycle and itertools.chain is a different approach from the existing answers that might come in handy if you have a lot of pre-existing lists that you want to append to in a round-robin fashion. It also works when your function returns more values than you have lists.
>>> from itertools import cycle, chain
>>> a, b = [], [] # new, empty lists only for demo purposes
>>> for l, v in zip(cycle([a, b]), (chain(*(f() for i in range(10))))):
... l.append(v)
...
>>> a
[5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
>>> b
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6]
I'd do
tmp = f()
a.append(tmp[0])
b.append(tmp[1])
Not sure how pythonic it is for you though.

Remove all occurences of a given value in multiple arrays at once

I have four arrays, say, A, B, C and D, of the same size NumElements, and I want to remove all the 0s in them. If A has a zero, B, C and D have one too, in the same position. So I was thinking to loop over the elements of A:
for n in range(NumElements):
if A[n]==0:
A.pop(n)
B.pop(n)
C.pop(n)
D.pop(n)
Of course, this doesn't work, because popping 0s from the arrays reduces their sizes, so I end up trying to access A[NumElements-1], when now A is only NumElements-m long. I know I should work with array copies, but the arrays are quite long and I'd like to keep memory consumption low, since I'm working in a Java virtual machine (don't ask :(((( ). Also, I'd like an approach which is efficient, but most of all readable (this code must be maintained by Python illiterates like me, so I need to KISS).
a,b,c,d = [filter(lambda i: i != 0, l) for l in [a,b,c,d]]
Filter each list removing elements that are not 0.
Edit,
Just to explain whats happening
Filter takes an expression and "filters" the list, by applying the function to everything in the list, everything that does not return True.
Lambda is a short hand for a function
So
a = [1,2,3,4,5,6,7,8]
def is_even(x):
return x % 2 == 0
filter(is_even, a)
If they all have zeros in the same place, then loop over the index in reverse and remove that index from each list:
for i in reversed(range(NumElements)):
if not A[i]:
del A[i], B[i], C[i], D[i]
By looping over the list in reverse, you keep the indices stable (only elements past the current index have been removed, shrinking only the tail of the lists). Since you are not using the return value of list.pop() (all you get is 0s anyway, right?), you may as well just use del on the list index instead.
I used reversed(range(NumElements)) here instead of calculating the more strenuous range(NumElements - 1, -1, -1); it is just as efficient but a lot more readable. The reversed() function returns an iterator, handling the reversed number sequence very efficiently. On Python 2, you can do the same with xrange():
for i in reversed(xrange(NumElements)):
Demo:
>>> A = [1, 2, 0, 4, 5, 0]
>>> B = [2, 4, 0, 10, 9, 0]
>>> C = [5, 3, 0, 10, 8, 0]
>>> D = [10, 3, 0, 1, 34, 0]
>>> for i in reversed(range(NumElements)):
... if not A[i]:
... del A[i], B[i], C[i], D[i]
...
>>> A, B, C, D
([1, 2, 4, 5], [2, 4, 10, 9], [5, 3, 10, 8], [10, 3, 1, 34])
Just work from the other end!
for n in range(NumElements-1,-1,-1):
if A[n]==0:
A.pop(n)
B.pop(n)
C.pop(n)
D.pop(n)
I think you could do smth like this. I don't know if it's pythonic enough.
A = [1, 2, 4, 0]
B = [6, 0, 4, 3, 9]
C = [12, 5, 32, 0, 90]
for row in [A, B, C]:
for i, v in enumerate(row):
if v == 0: del row[i]
or, if you sure that indexes of zero are equal in all lists:
for i in range(len(A) - 1, -1, -1):
if A[i] == 0:
for row in [A, B, C]:
del row[i]
Look at my other answer List accessing in Python. You can walk through list A and store indexes of 0s in temporary list and then pop them.
This is probably a hack but it's simple and it works
>>> a = [1,2,3]
>>> b = [1,10,99]
>>> c = [1,87,22]
>>> d = []
>>> d.extend([a,b,c])
>>> to_remove = 1
>>> [i.remove(to_remove) for i in d]
>>> d
[[2, 3], [10, 99], [87, 22]]
Note that this will remove all elements marked as to_remove not just zeros at the beginning, I'm assuming this is ok for you because you say that you want to remove all the zeros.

list match in python: get indices of a sub-list in a larger list

For two lists,
a = [1, 2, 9, 3, 8, ...] (no duplicate values in a, but a is very big)
b = [1, 9, 1,...] (set(b) is a subset of set(a), 1<<len(b)<<len(a))
indices = get_indices_of_a(a, b)
how to let get_indices_of_a return indices = [0, 2, 0,...] with array(a)[indices] = b? Is there a faster method than using a.index, which is taking too long?
Making b a set is a fast method of matching lists and returning indices (see compare two lists in python and return indices of matched values ), but it will lose the index of the second 1 as well as the sequence of the indices in this case.
A fast method (when a is a large list) would be using a dict to map values in a to indices:
>>> index_dict = dict((value, idx) for idx,value in enumerate(a))
>>> [index_dict[x] for x in b]
[0, 2, 0]
This will take linear time in the average case, compared to using a.index which would take quadratic time.
Presuming we are working with smaller lists, this is as easy as:
>>> a = [1, 2, 9, 3, 8]
>>> b = [1, 9, 1]
>>> [a.index(item) for item in b]
[0, 2, 0]
On larger lists, this will become quite expensive.
(If there are duplicates, the first occurrence will always be the one referenced in the resulting list, if not set(b) <= set(a), you will get a ValueError).

Categories