For loop iterating not executed with condition set inside [duplicate] - python

This question already has answers here:
Python list problem [duplicate]
(2 answers)
Closed 5 years ago.
I have a simple python loop that iterate over a 2D list which has 1000 sublist inside. Each sublist will contain 3 string values. I only want to change the sublists which are after the 365th sublist. I also have a very trivial condition which will decide if an operation will be applied to the element. My minimum code is as follows:
def change_list(arr):
for i in range(len(arr)):
if i < 365:
pass
else:
arr[i][1] = str(int(arr[i][1]) * 2)
When apply this function in main I'll simply invoke this function as: change_list(parsed_lines). For parsed lines, I'll just give a simple example:
parsed_lines = [["afkj","12","234"]] * 1000
My function will do the "multiply by 2" operation on all sublists, which is an unexpected behavior. I've tried not using conditions, but results in the same behavior.
for i in range(365, len(arr)):
arr[i][1] = str(int(arr[i][1]) * 2)
Then I tried the other way to iterate my arr as:
for line in arr:
if arr.index(line) < 365:
print("I'm less than 365")
else:
line[1] = str(int(line[1]) * 2)
but this iteration will never execute the block under else. I am very confused by this, hope someone can help.
Update:
The expected behavior should be:
For arr[0: 365], the sublist will stay the same as: [["afkj","12","234"]]
For arr[365:], the sublist will be: [["afkj","24","234"]]

Your problem, as described, is not in the program, but in the test set. parsed_lines = [["afkj","12","234"]] * 1000 creates a list of 1000 references to the same list ["afkj","12","234"]. When it is modified through any of those references (say, above 365), it is seen as modified through any of those references (even below 365). In other words, parsed_lines[0][0]='foo' makes all fisrt elements in all sublists 'foo'.

Related

double list in return statement. need explanation in python

So I was trying to complete this kata on code wars and I ran across an interesting solution. The kata states:
"Given an array of integers, find the one that appears an odd number of times.
There will always be only one integer that appears an odd number of times."
and one of the solutions for it was:
def find_it(seq):
return [x for x in seq if seq.count(x) % 2][0]
My question is why is there [0] at the end of the statement. I tried playing around with it and putting [1] instead and when testing, it passed some tests but not others with no obvious pattern.
Any explanation will be greatly appreciated.
The first brackets are a list comprehension, the second is indexing the resulting list. It's equivalent to:
def find_it(seq):
thelist = [x for x in seq if seq.count(x) % 2]
return thelist[0]
The code is actually pretty inefficient, because it builds the whole list just to get the first value that passed the test. It could be implemented much more efficiently with next + a generator expression (like a listcomp, but lazy, with the values produced exactly once, and only on demand):
def find_it(seq):
return next(x for x in seq if seq.count(x) % 2)
which would behave the same, with the only difference being that the exception raised if no values passed the test would be IndexError in the original code, and StopIteration in the new code, and it would operate more efficiently by stopping the search the instant a value passed the test.
Really, you should just give up on using the .count method and count all the elements in a single pass, which is truly O(n) (count solutions can't be, because count itself is O(n) and must be called a number of times roughly proportionate to the input size; even if you dedupe it, in the worst case scenario all elements appear twice and you have to call count n / 2 times):
from collections import Counter
def find_it(it):
# Counter(it) counts all items of any iterable, not just sequence,
# in a single pass, and since 3.6, it's insertion order preserving,
# so you can just iterate the items of the result and find the first
# hit cheaply
return next(x for x, cnt in Counter(it).items() if cnt % 2)
That list comprehension yields a sequence of values that occur an odd number of times. The first value of that sequence will occur an odd number of times. Therefore, getting the first value of that sequence (via [0]) gets you a value that occurs an odd number of times.
Happy coding!
That code [x for x in seq if seq.count(x) % 2] return the list which has 1 value appears in input list an odd numbers of times.
So, to make the output as number, not as list, he indicates 0th index, so it returns 0th index of list with one value.
There is a nice another answer here by ShadowRanger, so I won't duplicate it providing partially only another phrasing of the same.
The expression [some_content][0] is not a double list. It is a way to get elements out of the list by using indexing. So the second "list" is a syntax for choosing an element of a list by its index (i.e. the position number in the list which begins in Python with zero and not as sometimes intuitively expected with one. So [0] addresses the first element in the list to the left of [0].
['this', 'is', 'a', 'list'][0] <-- this an index of 'this' in the list
print( ['this', 'is', 'a', 'list'][0] )
will print
this
to the stdout.
The intention of the function you are showing in your question is to return a single value and not a list.
So to get the single value out of the list which is built by the list comprehension the index [0] is used. The index guarantees that the return value result is taken out of the list [result] using [result][0] as
[result][0] == result.
The same function could be also written using a loop as follows:
def find_it(seq):
for x in seq:
if seq.count(x) % 2 != 0:
return x
but using a list comprehension instead of a loop makes it in Python mostly more effective considering speed. That is the reason why it sometimes makes sense to use a list comprehension and then unpack the found value(s) out of the list. It will be in most cases faster than an equivalent loop, but ... not in this special case where it will slow things down as mentioned already by ShadowRanger.
It seems that your tested sequences not always have only one single value which occurs an odd number of times. This will explain why you experience that sometimes the index [1] works where it shouldn't because it was stated that the tested seq will contain one and only one such value.
What you experienced looking at the function in your question is a failed attempt to make it more effective by using a list comprehension instead of a loop. The actual improvement can be achieved but by using a generator expression and another way of counting as shown in the answer by ShadowRanger:
from collections import Counter
def find_it(it):
return next(x for x, cnt in Counter(it).items() if cnt % 2)

How to remove same value from one list and how to change ranges automatically [duplicate]

This question already has answers here:
Removing duplicates in lists
(56 answers)
Closed 1 year ago.
def my_list(enter_list):
#print(len(enter_list))--> 7 element
for i in range(0, len(enter_list)): # THE PROBLEM IS HERE
count = enter_list.count(enter_list[i])
if count >=2:
enter_list.pop(i)
return enter_list
print(my_list(["purple","coffee",2,6,2,"purple","steak"]))
At first there are 7 value in my list. But after I remove one of the same value from my list , my list's value is decreasing. İf I change the ranges range(0,len(enter_list)-2) like this. It works. I dont know how to change ranges automatically. ( I can change the ranges manually but it'll not work everytime. )
['coffee', 6, 2, 'purple', 'steak']
This is the output when I change the ranges manually.
Rather than attempt to modify the range you are iterating over, I would suggest you create a copy of the enter_list list and pop() the elements out of that list. That way you will be iterating over the full list of items, but you will return the modified version of the list without having to dynamically alter the range of your loop, which I don't believe is possible.
To quickly show the technique in your code:
def my_list(enter_list):
output_list = enter_list.copy()
for i in range(0, len(enter_list)):
count = enter_list.count(enter_list[i])
if count >=2:
output_list.pop(i)
return output_list
Here you return output_list, which will be your filtered version, but by copying the enter_list, you ensure you are iterating over the full set.
Just to add: using pop(i) here will likely result in you popping something out of range near the end of the loop, so this type of loop iterating over the elements might work best:
for items in enter_list:
output_list.pop(item)
Then you ensure you are not going to pop() on an out of range index.
You could do like this:
def my_list(lst):
i = 0
while True:
cnt = lst.count(lst[i])
if cnt > 1:
lst.pop(i)
i += 1
if len(lst) == i:
break
return lst
print(my_list(["purple","coffee",2,6,2,"purple","steak"]))
OUTPUT
['coffee', 6, 2, 'purple', 'steak']
The problem with your approach is that, in the for loop, the len of the list is only evaluated once - at the beginning. The reason why decreasing by 2 makes it work is because there are 2 elements which have one duplicate, so the loop does not go out of range.

Variable in while loop not incrementing after for loops [duplicate]

This question already has answers here:
Appending to list with loop
(2 answers)
Closed 3 years ago.
I am trying to create a cartesian product of the alphabet with loops. I have for loops that create the desired output but i in my while loop is never reached for some reason.
This loop is running forever in a jupyter lab notebook.
lower_az = [chr(ord('a') + i) for i in range(26)]
i=0
n=2
lst = lower_az.copy()
final_list = []
while i < n:
for let in lst:
for j in range(26):
strng = let + lower_az[j]
lst.append(strng)
i += 1
final_list.append(lst)
Unless I am missing something obvious the variable i should increment until it reaches n and stop the while loop at the desired length of strings.
You are changing the list you are iterating over. The problem is not the while-loop, it's the lst.append(strng) while iterating for let in lst.
#blue_note is correct - Python doesn't behave well when you change a list you're iterating over.
It looks like this is just a typo, though: you've got final_list all ready to receive the elements. To fix this, change:
lst.append(strng)
to
final_list.append(strng)
and drop final_list.append(lst) and your program appears to work fine.

Iterating - Python [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 5 years ago.
Trying to remove negative numbers from a list. I kept running into an issue where two successive negative numbers did not get removed. My code is quite simple:
numbers = [-5, 1, -3, -1]
def remove_neg(num_list):
for item in num_list:
if item < 0:
num_list.remove(item)
print(remove_neg(numbers))
#[1, -1]
I found the answer online after trying 4 different versions of my code and pulling a few hairs out of my head. The answer I found assigned r = numbers[:] and then removed items from r instead of the initial list.
def remove_neg(num_list):
r = numbers [:]
for item in num_list:
if item < 0:
r.remove(item)
print(r)
I understand this concept to have two list variables point to separate data. What I don't understand, is why my initial code doesn't work. Shouldn't for i in numbers: iterate through every item in the list? Why would two successive numbers not be treated the same? I've scoured looking for why and can't seem to find an answer.
In the first example you're modifying the list while iterating over it, which, as you've seen, messes up the iteration. While the second example works, it's very inefficient, and it would be much easier to use a list comprehension to construct a new list without the negatives:
def remove_neg(num_list):
return [x for x in num_list if x > 0]

Python - large list efficiency [duplicate]

This question already has answers here:
How do I remove duplicates from a list, while preserving order?
(31 answers)
Closed 8 years ago.
I have a function, unique(a) that takes a list, a, of numbers and returns only one of each value. At the same time, it maintains the order of the list. I also have a function, big_list(n) that generates a list of len(n).
The reason to why I reverse the direction of the list is so that when removing values, It removes them from the back of the original list, just to make the modified list more clean and readable when comparing it to the original list.
The function works when I have a relatively small length of the list I'm creating, but when I get to larger lengths, such as 1,000,000 for ex, the execution time takes FOREVER.
If anyone can help me by making my function a lot faster, that would be great!
FYI : I need to use a set somewhere in the function for the assignment I am working on. I still need to remove list items from the back as well.
Thanks in advance!
def big_list(n) :
# Create a list of n 'random' values in the range [-n/2,n/2]
return [ randrange(-n//2, n//2) for i in range(n) ]
def unique(a) :
a = a[::-1]
b = set(a)
for i in b :
while a.count(i) != 1 :
a.remove(i)
a.count(i)
a = a[::-1]
return a
Your algorithm is doing a lot of extra work moving elements around. Consider:
def unique(a):
b = set()
r = []
for x in a:
if x not in b:
r.append(x)
b.insert(x)
return r
Every time you call a.count(i) it loops over the entire list to count up the occurrences. This is an O(n) operation which you repeat over and over. When you factor in the O(n) runtime of the outer for i in b: loop the overall algorithmic complexity is O(n2).
It doesn't help that there's a second unnecessary a.count(i) inside the while loop. That call doesn't do anything but chew up time.
This entire problem can be done in O(n) time. Your best bet would be to avoid list.count() altogether and figure out how you can loop over the list and count elements yourself. If you're clever you can do everything in a single pass, no nested loops (or implicit nested loops) required.
You can find a thorough benchmark of "unique" functions at this address. My personal favorite is
def unique(seq):
# Order preserving
seen = set()
return [x for x in seq if x not in seen and not seen.add(x)]
because it's the fastest and it preserves order, while using sets smartly. I think it's f7, it's given in the comments.

Categories

Resources