Python 2.7.5 .count() being ignored? - python

I'm trying to debug some code for someone, and have run into a rather odd scenario. The purpose of the code is to search for duplicates in a given list and then return a list with no duplicates. (Note, the person writing the code chose to simply delete the duplicates from the list, when I personally would just add each value to a new list. However, I am still intrigued by the oddity). The code is as follows:
def remove_duplicates(duplicates):
duplicates_del = duplicates
for i in duplicates_del:
if duplicates_del.count(i) > 1:
duplicates_del.remove(i)
return duplicates_del
remove_duplicates([3, 3, 3, 3, 3, 3])
When run, the code will return [3, 3, 3] and after some debugging, I've found that the code will work fine until duplicates_del.count(i) is equal to 4. On the next round, it will completely skip everything inside the for statement and go directly to the return statement, resulting in the answer that we get.
I have learned that changing the if statement to while duplicates_del.count(i) > 1: will make the code run flawlessly.
I've looked into the code for the debugger, and learned that there is a breakpoint class that can ignore counts. Is the if statement somehow triggering this breakpoint, or is there another reason why the code doesn't run fully with an if statement instead of a while loop?

The reason this is happening is because you're iterating over a list while you're removing items. This will mostly always result in unexpected results. Take a look at:
L = [1, 2, 3, 4, 5]
for item in L:
if item == 1 or item == 2 or item == 3:
L.remove(item)
print L
The output is:
[2, 4, 5]
Notice that 2 was never removed. If we print item in each loop, we get:
1
3
5
After python removes 1, the order of the list will change, and 2 won't necessarily be the next item in the loop (in fact, 3 is). Notice how 4 is also skipped.
To avoid such behaviour, you must iterate over a copy of the list. Sadly, what you did was not making a copy. Doing duplicates_del = duplicates will make both objects reference the same identity, so changing an element in one will change it in the other.
You should do this:
def remove_duplicates(duplicates):
for i in duplicates[:]: # Creates a copy of the list
if duplicates.count(i) > 1:
duplicates.remove(i)
return duplicates

You are deleting from the list as you loop over it.
Usually, this means that the item following one that is deleted is skipped over.
In this case remove is removing the first matching element each time, so the entire list is being shifted down. The list iterator doesn't see that the list has changed, so increments to the next item.

Related

How do I set a variable to a index of a list?

l = [1,2,4]
for i in range(3):
a = l[i]
im tryihng to do that above but it isn't working and it says
'builtin_function_or_method' object cannot be interpreted as an integer. can anyone say why this is and tell me how to fix this
edit:There was something earlier in the code before this and it was because i was doing .lower and not .lower() sorry guys
Let's explain what your code does before solving it. Edit available at the bottom of the answer
for i in range(3):
a = l[i]
What this does is creates a "range" of numbers from 0 to 2, however its supposed to go from 1 (or 0) to 3. Why? Computers have been trained to start counting from 0 instead of 1 like a normal human and subsequently they are 1 less. (This is a simplified one, there's a longer one that you'll learn over time)
Now your 2nd line of code assigns the variable a the value of one of the items in the list l. Let's look at what value 'a' would be assigned during this time
1 (1st item)
2 (2nd item)
IndexError: Out of range error (there is no 3rd item)
So how do you solve this? One way is to add more items to your list l.
So let's then add 2 more items into l (3 and 4)
This is our variable l now
l = [1, 2, 3, 4]
Here's our output now
1 (1st item)
2 (2nd item)
3 (3rd item)
As you noticed, it skips the 4th item since we specified to only iterate over 3 items in the list. If you wanted to "iterate over a list" look no further!.
Observe
for i in l:
print(i)
This creates a for loop that goes over each item in the list l one by one from start to finish that allows you to see the current item in the variable i! In our code, it simply prints the variable i each time the for loop goes to the next item in the list.
1
2
3
4
And simply stops once it reaches the end!
Don't worry, we've all been there while learning code :)
UPDATE: Based on what you were saying, I'm assuming if you wanted to assign the variable a the 2nd place in the list 'l' you would use
a = l[1]
Yes to get the 2nd place you need to type 1. The same goes for accessing the 1st item, you change the l[1] with l[0]. This is because computers count from 0 instead of human's traditionally counting from 1
the code you wrote isn't even syntactically correct.
l = [1,2]
for i in range(len(l)):
# you had no colon, so the first error you should have gotten is a syntax error.
# the second error you would have gotten is a value error, because you try to set 'a' to values
# that don't exist. a more dynamic way to do it anyways is to cycle through the
# length of the list.
a = l[i]
im not sure why you want to do this, as it will result in a = 2. staying true to your question, the only reasonable way to do what you're asking is something as easy as this.
a = l[2] # or whatever index you're trying to get
you're method, even if it compiled correctly, would not have accomplished what you say you want.
as mentioned by 'meh' and 'shriakhilc', keep in mind that indexing starts at 0, so the list l would only have indexes of 0 and 1.

How do i 'replace' an array by filling an empty array's elements using pop method from another array?

I'm trying to implement a stack in python and I'm experimenting with the list data structure. If i want to use the pop method to 'fill' an empty array by using the elements from an existing array, what do I do?
# Implementing Stacks in Python through the given list structure
practiceStack = []
practiceStack.append(['HyunSoo', 'Shah'])
practiceStack.append('Jack')
practiceStack.append('Queen')
practiceStack.append(('Aces'))
# printing every element in the list/array
for i in practiceStack:
print(i)
# since stacks are LIFO (last in first out) or FILO (first in last out), the pop method will remove the first thing we did
emptyArrayPop = []
This is what I tried (by using a for loop) and keep getting a use integers not list error
for i in practiceStack:
emptyArrayPop[i].append(practiceStack.pop)
print(emptyArrayPop)
The pop function is a function — not a value. In other words, practiceStack.pop is a pointer to a function (you can mostly ignore this until you've spent more time around code); you likely want this instead:
practiceStack.pop()
You also need to append to the list; when adding something with append, the List will automatically add it at the end; you do not need to provide an index.
Further explanation: The List.append method will take the value that you pass to it and add that to the end of the List. For example:
A = [1, 2, 3]
A.append(4)
A is now [1, 2, 3, 4]. If you try to run the following:
A[2].append(4)
...then you are effectively saying, "append 4 to the end of the value at position-2 in A", (`A[2] is set to 3 in the above example; remember that python lists start counting at 0, or are "0-index".) which is like saying "Append 4 to 3." This makes no sense; it doesn't mean anything to append an integer to another integer.
Instead, you want to append to the LIST itself; you do not need to specify a position.
Don't get this confused with assigning a value to a position in a List; if you were setting a value at an existing position of a list, you can use the = operator:
>>> B = [1, 2, 3]
>>> B[2]
3
>>> B[2] = 4
>>> print(B)
[1, 2, 4]
>>> B.append(8)
>>> print(B)
[1, 2, 4, 8]
So to answer your original question, the line you want is the following:
emptyArrayPop.append(practiceStack.pop())
(note the [i] has been removed)
[edit] Not the only issue, as #selcuk pointed out.
You will also need to fix the way you're accessing data in the practiceStack list, as you cannot edit a list (calling pop modifies the list in-place) when you are iterating over it.
You will need to iterate over the integer index of the list in order to access the elements of practiceStack:
for i in range(len(practiceStack)):
emptyArrayPop.append(practiceStack.pop())

Why does this function work, where the for loop apparently fills the dict with values from the argument to allow a comparison?

Here is a practice interview question and the correct answer below it that fulfills the question. The problem is that I don't see how this function works. I explain my confusion below the answer.
Given an array a that contains only numbers in the range from 1 to
a.length, find the first duplicate number for which the second
occurrence has the minimal index. In other words, if there are more
than 1 duplicated numbers, return the number for which the second
occurrence has a smaller index than the second occurrence of the other
number does. If there are no such elements, return -1.
Answer:
def firstDuplicate(a):
oldies={}
notfound=True
for i in range(len(a)):
try:
if oldies[a[i]]==a[i]:
notfound=False
return a[i]
except:
oldies[a[i]]=a[i]
if notfound:
return -1
So the function creates an empty dict, oldies. But then within the for loop, I don't understand how this line works:
if oldies[a[i]]==a[i]:
To me it appears that the == operator compares the indexed values of an empty dict, "oldies," to the argument that would be a list like this, for example:
a = [2, 4, 1, 3, 4, 5, 1, 5, 7, 8, 2, 4,]
But obviously "oldies" is not empty when this comparison is done. What's going on here?
I'm going to break down that answer and explain a number of the issues with it.
Explanation
Firstly, the function creates an empty dict and a boolean. Then it iterates through a range from 0 to n, which represents the length of the input list. From there it will try to compare the value at index i in the list with a value from the dictionary, which is initially empty. Since there is no proper key in the dictionary, it will throw an KeyError. This will be caught by the except statement and add to dictionary. If the first value in the input list is 'a', then the dict will now look like {'a': 'a'}. Assuming that 'a' appears later in the list, it will eventually catch find and return that value. If it finds no duplicates, it returns -1.
Issues
It is not necessary to iterate over a range to iterate over a list. Iterating over the list directly will not require checking it's length or creating a range object, so it will likely be more performant.
Creating the boolean at the beginning and using it at the end is redundant because any call to return will exit the function immediately. Therefore, if a duplicate is found the return in the if block will exit and nothing after the loop will be called.
Using a dictionary is a bad choice of structure. There is more overhead space because it needs to maintain keys, values, and their relationships. Something like a set, or even a list would be a much better choice.
Assuming we change oldies to be a set and are iterating over the list directly, the whole conditional block in that code could be reduced to a simple in statement. This also eliminates the final conditional, as mentioned above, by proper use of return.
Even though I'm advising not to use it in this case, the except block should
catch explicit exceptions instead of a general catchall. Something like except KeyError: should have been used.
The end result would look something like:
def firstDuplicate(a):
oldies = set()
for i in a:
if i in oldies:
return i
else:
oldies.add(i)
return -1
print(firstDuplicate(['a', 'b', 'c', 'b', 'a', 'c']))
Result: b
There may be some even better solutions out there using itertools or some other package.

PYTHON get the last item of a list even if empty

My Python script opens 2 threading.Threads() with the following functions :
Stuff() : function appending stuff to a list (global var) if stuff happens in a big loop.
Monitor() : function displaying the last item added to the list every second with additional info.
The purpose of these 2 threads is that Stuff() contains a loop optimized to be very fast (~ 200 ms / iteration) so printing from inside would be pointless. Monitor()takes care of the output instead.
At the beginning, I set list = [], then start the threads. Inside Monitor() I get the last item of the list with list[-1] but if no stuff happend before, the list is still empty and the Monitor() raises an IndexError: list index out of range.
Is there a simple way (no try or if not list) to display None instead of an error if the list is empty ?
Here's a way, although it looks odd:
(list or [None])[-1]
I find this more readable than #Alex's answer
lst = [1, 3, 2]
print None if not lst else lst[-1]
Edit: Though must admit, it is the first time I encountered that usage. Always thought those expressions returned bool type :)
You can use it also like this:
[1, 2, 3, 4][-1:] -> [4]
[][-1:] -> []
[][-1:] or None -> None

how can I use remove for this code(2)?

In the last code , I tried to use one array to remove information.
In this one, I used three arrays to remove information as below:
class student(object):
def __init__(self):
self.c=0
self.x=[]
self.y=[]
self.z=[]
def vrod(self):
self.x.append(input("enter name:"))
self.z.append(int(input("enter #:")))
self.y.append(int(input("enter score:")))
self.c+=1
def remove(self):
self.p=int(input("enter student number:"))
for self.i in range(len(self.x)):
if self.p==self.z[self.i]:
del(self.x[self.i])
del(self.y[self.i])
del(self.z[self.i])
def report(self):
for self.i in range(len(self.x)):
print("name:",self.x[self.i],"st number:",self.z[self.i],"score:",self.y[self.i])
a=student()
while True:
print("o=1:::add ","o=2:::remove ","o=3:::report ","o=4:::exit")
o=int(input("enter menu:"))
if o==1:
a.vrod()
elif o==2:
a.remove()
elif o==3:
a.report()
elif o==4:
break
Again, I have problem with removing.As I enter numbers for removing, I get error as it is
"if self.p==self.z[self.i]: IndexError: list index out of range".
Do you know what is the problem?
Which of them is more common? Writing a code with one array or three arrays.
Your problem is that you're iterating over a loop and making changes to the list you're looping over while doing so. The impulse to do so is understandable, but unless you know what you're doing you should just avoid that. Anyway, lets take a look at what you're doing and how it impacts the loop and the list. Removing the extra stuff, the basics of it is this:
def remove(x):
for i in range(len(x)):
del(x[i])
#Do other stuff
Now, lets give x some dummy values:
values = [4, 10, 23]
remove(values)
Now before we run the code, it's useful to know exactly what range(len(x)) does, which is that it creates a new list that counts from 0 in increments of 1 (as long as nothing else is specified), and the amount of elements in it is equal to the length of the input list.
so lets feed range(len(x)) our dummy value, [4, 10, 23]. The output is [0, 1, 2]
So if we go back to the for loop, this means that almost no matter what we do after the for loop is initialized, the for loop will count through this newly generated list, [0, 1, 2]. So with all of that out of the way, lets actually run the code line by line.
#first loop
#i = 0
del(x[0])
#x=[10, 23]
This deletes element 0 in the list, 4, which leaves us with x=[10, 23] Notice how since deleting this element, 10 and 23 switched places, and the list became 1 element shorter. Still the list we're looping over remains the same, [0, 1, 2] So lets see what happens next loop:
#second loop
#i = 1
del(x[1])
x = [10]
This deletes element 1 in the list, which is now 23, leaving us with x=[10]. Notice how we just skipped an element we wanted to delete. Not a good sign. Any who, onto the final loop:
#third loop
#i = 2
del(x[2])
#IndexError
This is where Python sees a clear error and is forced to stop the program. at this point if you do len(x) it will return 1, which means the last element in the list will have the reference, 0. So when you then call del(x[2]), you're essentially telling Python to remove something non-existent.
So, now we have a decent overview of what went wrong, then how do we make this work? One way, would be to turn the solution on its head. Instead of deleting elements we want to get rid of in the for loop, we make a new list and add the elements to that list that we want to keep. I removed the if statement in the above example for simplicitys sake, but I'm adding it back in the solution to demonstrate how to do this when you don't necessarily want to remove all elements in a list. I'm not going to walk through it like I did with the previous example, so look at it and analyze it and try to understand why it works the way it works (the "#condition where you want to delete:" is pseudocode, so don't interpret that directly):
def remove(x):
rest_x = []
for i in range(len(x)):
if not #condition where you want to delete:
rest_x.append(x[i])
return rest_x
I hope this was helpful

Categories