How to simultaneously iterate and modify list, set, etc? - python

In my program I have many lines where I need to both iterate over a something and modify it in that same for loop.
However, I know that modifying the thing over which you're iterating is bad because it may - probably will - result in an undesired result.
So I've been doing something like this:
for el_idx, el in enumerate(theList):
if theList[el_idx].IsSomething() is True:
theList[el_idx].SetIt(False)
Is this the best way to do this?

This is a conceptual misunderstanding.
It is dangerous to modify the list itself from within the iteration, because of the way Python translates the loop to lower level code. This can cause unexpected side effects during the iteration, there's a good example here :
https://unspecified.wordpress.com/2009/02/12/thou-shalt-not-modify-a-list-during-iteration/
But modifying mutable objects stored in the list is acceptable, and common practice.
I suspect that you're thinking that because the list is made up of those objects, that modifying those objects modifies the list. This is understandable - it's just not how it's normally thought of. If it helps, consider that the list only really contains references to those objects. When you modify the objects within the loop - you are merely using the list to modify the objects, not modifying the list itself.
What you should not do is add or remove items from the list during the iteration.

Your problem seems to be unclear to me. But if we talk about harmful of modifying list during a for loop iteration in Python. I can think about two scenarios.
First, You modify some elements in list that suppose to be used on the next round of computation as its original value.
e.g. You want to write a program that have such inputs and outputs like these.
Input:
[1, 2, 3, 4]
Expected output:
[1, 3, 6, 10] #[1, 1 + 2, 1 + 2 + 3, 1 + 2 + 3 + 4]
But...you write a code in this way:
#!/usr/bin/env python
mylist = [1, 2, 3, 4]
for idx, n in enumerate(mylist):
mylist[idx] = sum(mylist[:idx + 1])
print mylist
Result is:
[1, 3, 7, 15] # undesired result
Second, you make some change on size of list during a for loop iteration.
e.g. From python-delete-all-entries-of-a-value-in-list:
>>> s=[1,4,1,4,1,4,1,1,0,1]
>>> for i in s:
... if i ==1: s.remove(i)
...
>>> s
[4, 4, 4, 0, 1]
The example shows the undesired result that raised from side-effect of changing size in list. This obviously shows you that for each loop in Python can not handle list with dynamic size in a proper way. Below, I show you some simple way to overcome this problem:
#!/usr/bin/env python
s=[1, 4, 1, 4, 1, 4, 1, 1, 0, 1]
list_size=len(s)
i=0
while i!=list_size:
if s[i]==1:
del s[i]
list_size=len(s)
else:
i=i + 1
print s
Result:
[4, 4, 4, 0]
Conclusion: It's definitely not harmful to modify any elements in list during a loop iteration, if you don't 1) make change on size of list 2) make some side-effect of computation by your own.

you could get index first
idx = [ el_idx for el_idx, el in enumerate(theList) if el.IsSomething() ]
[ theList[i].SetIt(False) for i in idx ]

Related

Using For Loops in Recursive Function to get every possible Combination

I want to create a funciton getCombinations that given a list of positive integers and a maximum amount, appends all the possible combinations of the given integers that sum to the given amount to a list outside of the function.
For example:
combinations=[]
getCombinations([5,2,1], 10)
print(combinations)
should return:
[[5, 5], [5, 2, 2, 1], [5, 2, 1, 1, 1], [5, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2], ... , [1, 1, 1, 1, 1, 1, 1, 1, 1, 1,]
I tried to use a recursive function that loops over the given integers, and appends them to the list current_combinations.
If the sum of that list equals the given amount, it should append the current_combination.
If the sum is smaller it goes one level deeper and appends new numbers from the list.
combinations = []
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
for num in num_types:
current_combi.append(num)
if sum(current_combi) == max_amount:
combinations.append(current_combi)
elif sum(current_combi) < max_amount:
getCombinations(num_types, max_amount, current_combi)
current_combi = current_combi[:-1]
getCombinations([5, 2, 1], 10)
print(combinations)
But this only outputs a fraction of the anticipated result:
[[5, 5], [5, 2, 2, 1], [5, 2, 2, 1], [5, 2, 1, 2], [5, 2, 1, 1, 1], [5, 2, 2, 1], [5, 2, 2, 1], [5, 2, 1, 2], [5, 2, 1, 1, 1]]
Help would be much appreciated, thank you.
There are two main problems here.
First, there is the algorithmic problem, which is that in every recursive call, you start at the beginning of the list of possible values. That will inevitably lead to duplicated lists. You want the produced lists to be sorted in descending order (or, at least, in the same order as the original list), and you need to maintain that order by not recursing over values you have already finished with.
The second problem is subtler; it has to do with the way you handle the current_combi arrays, with emphasis on the plural. You shouldn't use multiple arrays; when you do, it's very easy to get confused. What you should do is use exactly one array, and only make a copy when you need to add it to the result.
You might need to pull out a pad of paper and a pencil and play computer to see what's going on, but I'll try to describe it. The key is that:
current_combi.append(num) modifies the contents of current_combi;
passing current_combi as a function argument does not create a new list;
current_combi = current_combi[:-1] does create a new list, and does not modify the old one.
So, you enter getCombinations and create a current_combi list. Then you push the first value onto the new list (so it's now [5] and recursively call getCombinations, passing the same list. Inside the recursive call, the first value is appended again onto the list (which is still the same list); that list is now [5, 5]. That's a valid result, so you add it to the accumulated results, and then you create a new current_combi. At this point, the original current_combi is still [5, 5] and the new one is [5]. Then the for loop continues (in the recursive call), but the rest of that loop no longer has access to the original current_combi. So we can fast forward to the end of the for loop, ignoring the recursive subcalls, and return to the top level.
When we return to the top level, current_combi is the list which was originally created, and that list had 5 appended to it twice, once in the top-level for loop and again when the first recursive call started. So it's still [5. 5], which is unexpected. A fundamental property of recursive backtracking is that the problem state variable be the same before and after each recursive call. But that property has been violated. So now at the end of the top-level for loop, an attempt is made to remove the 5 added at the beginning. But since that list is now [5, 5], removing the last element produces [5] instead of []. As a result, lists starting with 2 are never produced, and lists starting 5, 2 are produced twice.
OK, let's fix that. Instead of making copies of the list at uncontrolled points in the execution, we'll just use one list consistently, and make a copy when we add it to the accumulated results:
# This one still doesn't work. See below.
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
for num in num_types:
current_combi.append(num)
if sum(current_combi) == max_amount:
combinations.append(current_combi[:]) # Add a copy to results
elif sum(current_combi) < max_amount:
getCombinations(num_types, max_amount, current_combi)
current_combi.pop() # Restore current_combi
But that doesn't actually fix the first problem noted above: that the recursion should not reuse values which have already been used. Instead of looping over the values in num_types, we need to loop over its suffixes:
def getCombinations(num_types, max_amount, current_combi=None):
if current_combi is None:
current_combi = []
values = num_types[:] # Avoid modifying the caller's list
while values:
# values.pop(0) removes the first element in values and returns it
current_combi.append(values.pop(0))
if sum(current_combi) == max_amount:
combinations.append(current_combi[:]) # Add a copy to results
elif sum(current_combi) < max_amount:
getCombinations(values, max_amount, current_combi)
# Restore current_combi
current_combi.pop()
In the above, I was trying to roughly follow the logic of your original. However, the handling of values could be made more efficient by passing the starting index in the list instead of modifying the list. Also, there is no need to rescan the candidate combination in order to add up its value, since we can just add the value we just added (or, as in the following code, subtract it from the target). Finally, since a lot of the arguments to the recursive call are always the same, they can be replaced by a closure:
def getCombinations(num_types, max_amount):
results = []
candidate = []
def helper(first_index, amount_left):
for index in range(first_index, len(num_types)):
value = num_types[index]
if amount_left == value:
results.append(candidate + [value])
elif amount_left > value:
candidate.append(value)
helper(index, amount_left - value)
candidate.pop()
helper(0, max_amount)
return results
That's still not the optimal implementation, but I hope it shows how to evolve this implementation.

How can I convert a list of values into an index/slice from a list?

I have a list that contains many elements, where each element represents an input file, that I want to dynamically subset using the values contained within another list. For example, I have some code that dynamically generates lists that I want to use to define the sub-samples such as
[0, 1, 2, 3]
and
[1, 2, 3, 4]
But I want to use the start and end elements of each of these lists to define an slice range to be applied to another list. In other words, I want the two above lists to be converted into slices that look like this
[0:3]
and [1:4]
But I don't know how to do this, and to be honest I'm not even sure the correct terminology to use to search for this. I have tried searching stack overflow for 'dynamically generate slices from lists' or even 'dynamically generate data slice' (an variants that I can think of along those lines) without any success.
Here are a few more details:
thislist = ['2019/12/26/fjjd', '2019/12/26/defg', '2020/01/09/qpfd', '2020/01/09/tosf', '2020/01/16/zpqr', '2020/01/15/zpqr', '2020/01/15/juwi']
where someIndexSlice is
[0:3]
and generated from a list that looks like this
[0,1,2,3]
thislist[someIndexSlice] = ['2019/12/26/fjjd', '2019/12/26/defg', '2020/01/09/qpfd', '2020/01/09/tosf']
So my questions are:
How can I accomplish this?
What sort of terminology should I use to describe what I am trying to accomplish?
Thanks
You can use the built-in slice function:
>>> lst = [0, 1, 2, 3]
>>> as_slice = slice(lst[0], lst[-1], lst[1] - lst[0])
>>> as_slice
slice(0, 3, 1) # which is same as [0:3]
And then to check if it works correctly:
>>> test = [1, 5, 3, 7, 8]
>>> test[as_slice]
[1, 5, 3]
>>> test[0:3]
[1, 5, 3]
NOTE:
This implementation assumed your lists are equidistant, and sorted.

I want to implement bubblesort using python list comprehension but it displays blank

I want to implement bubblesort using python list comprehension but it displays blank. Tried using assignment operators to swap (l[j]=l[j+1]) but it throws an error as list comprehension does not support assignment
l = [8, 1, 3, 5, 4, 6, 7, 2]
newlist= [ [(l[j],l[j+1]),(l[j+1],l[j])] for i in range(1,len(l)-1) for j in range(0,len(l)-1) if l[j]>l[j+1] ]
Expected output is: 1, 2, 3, 4, 5, 6, 7, 8
But I am getting the output as [].
This fails from a conceptual problem, which is that every filtered result must produce a value to include in the list. Your list comprehension has no capability to store intermediate results -- it's doomed to failure. You have to determine whether to emit a value, and which value to emit, given only the original list, i, and j. That information does not exist with bubble-sort logic.
For instance, consider the first nested iteration. You have this information at hand:
l = [8,1,3,5,4,6,7,2]
i = 1
j = 0
Given this, you must decide right now whether or not to put information into y our final list -- if so, which information to put there. You cannot defer this to a second pass, because you have no temporary storage within the comprehension.
See the problem?

Rearrange list in-place by modifying the original list, put even-index values at front

I am relatively new to python and I am still trying to learn the basics of the language. I stumbled upon a question which asks you to rearrange the list by modifying the original. What you are supposed to do is move all the even index values to the front (in reverse order) followed by the odd index values.
Example:
l = [0, 1, 2, 3, 4, 5, 6]
l = [6, 4, 2, 0, 1, 3, 5]
My initial approach was to just use the following:
l = l[::-2] + l[1::2]
However, apparently this is considered 'creating a new list' rather than looping through the original list to modify it.
As such, I was hoping to get some ideas or hints as to how I should approach this particular question. I know that I can use a for loop or a while loop to cycle through the elements / index, but I don't know how to do a swap or anything else for that matter.
You can do it by assigning to a list slice instead of a variable:
l[:] = l[::2][::-1] + l[1::2]
Your expression for the reversed even elements was also wrong. Use l[::2] to get all the even numbers, then reverse that with [::-1].
This is effectively equivalent to:
templ = l[::2][::-1] + l[1::2]
for i in range(len(l)):
l[i] = templ[i]
The for loop modifies the original list in place.

Python List Doesn't Update

I am trying to solve a seemingly simple problem on Python. I am trying to update the last 3 element of a list to other integers. Here is the code:
list = [0,1,2,3,4]
for i in xrange(len(list[2:])):
list[2:][i] = 44444
print list
But when I print the list, it still remains the same. I observe the changes in debugger and I found that the list doesn't update in the loop. I tried searching elsewhere on Google and Stack Overflow, but none of them provide answers to this simple problem.
Let me know what you guys think about this. Thanks.
A slice of a list is a new list; it doesn’t reference the original or anything like that. Assign to list[2 + i] instead. (You can avoid making an extra slice to get the length in the same way.)
list = [0, 1, 2, 3, 4]
for i in xrange(len(list) - 2):
list[2 + i] = 44444
print list
A slice creates a new list, unless you assign to it:
>>> values = [0, 1, 2, 3, 4]
>>> values[-3:] = [4444] * 3
>>> values
[0, 1, 4444, 4444, 4444]
By using for i in xrange(len(list[2:])): you are reducing the length of list from 5 to 3. Your loop would iterate only for the first 3 list items.
Instead you could do something like this:
a=[0,1,2,3,4,5]
for i in range(3,len(a)):
a[i]=444
print(a)

Categories

Resources