What's wrong with my python selection sort? - python

I can't figure out why the python program produces the following output:
c:\Python Programs>selection_sort.py
[7, 4, 2, 9, 6]
[2, 4, 7, 9, 6]
[2, 6, 7, 9, 4]
[2, 6, 4, 9, 7]
Traceback (most recent call last):
File "J:\Python Programs\Python Practice\selection_sort.py", line 11, in <modu
le>
num_list[i], num_list[min_num] = num_list[min_num], num_list[i]
IndexError: list index out of range
c:\Python Programs>
I think I understand the list index out of range part, but I'm not sure about why the 6 becomes the second element when i = 1. Didn't the machine read my if statement?
Here is the code below:
num_list = [7,4,2,9,6]
len_num_list = len(num_list)
print num_list
print""#print empty string to separate the original list from the following iterations
for i in range(0,len_num_list):
min_num = min(num_list[i:]) #finds minimum number in list to the right of i
if min_num>num_list[i]:
min_num = num_list[i]
num_list[i], num_list[min_num] = num_list[min_num], num_list[i]
print num_list

First, let's note that in your snippet:
min_num = min(num_list[i:]) #finds minimum number in list to the right of i
if min_num>num_list[i]:
min_num = num_list[i]
the if will never, ever match -- since min_num is the minimum of a sub-list that starts with num_list[i], it can't possibly, ever, under any circumstance, be greater than the latter.
So, lose the last two of these three statements -- they're about as useful as checking if 2+2 != 4::-).
Next, let's note that you don't really want min_num to be a value (which is what your call to min gives you) -- you want it to be an index into the list, in order to perform the swap:
num_list[i], num_list[min_num] = num_list[min_num], num_list[i]
But trying to turn a value into an index via the index method is quite an iffy path: if the input list can have any duplicates, index will always locate the first one of them, and that might quite possibly tangle you up. I personally would choose not to go there.
Rather consider the more direct path of finding the minimum index using the corresponding value via the key= feature of min! I.e:
for i in range(0,len_num_list):
min_ind = min(range(i, len_num_list),
key=lambda j: num_list[j])
num_list[i], num_list[min_ind] = num_list[min_ind], num_list[i]
print num_list
If you're not familiar with the key= feature of many Python built-ins (min, max, sorted, ...), it's really a good thing to learn.
It sorts (or gives the min, or max, or) a certain sequence, with the comparisons done after passing each item of the sequence through the "key extraction function" you pass as key=. Here, you want "the index of the minimum" and you get that by picking the min index with a key= of the corresponding look-up of each index into the list.
I personally dislike lambda and might use key=numlist.__getitem__, but that's not very readable either -- most readable is always to use def (and I'd do the same for that swap functionality), e.g...:
def item_in_list(index): return num_list[index]
def swap(i, j): num_list[i], num_list[j] = num_list[j], num_list[i]
for i in range(0,len_num_list):
min_ind = min(range(i, len_num_list), key=item_in_list)
swap(i, min_ind)
print num_list
which I find to be the most readable and elegant approach to this task.

The problem is that min(num_list[i:]) returns a number from the list, not an index into that list. You can use the index method to get the index corresponding to min(num_list[i:]). Thus, try:
num_list = [7,4,2,9,6]
len_num_list = len(num_list)
print num_list
print""#print empty string to separate the original list from the following iterations_
for i in range(0,len_num_list):
min_num = min(num_list[i:]) #finds minimum number in list to the right of i
j = num_list.index(min_num)
if min_num>num_list[i]:
min_num = num_list[i]
num_list[i], num_list[j] = num_list[j], num_list[i]
print num_list
This produces the output:
[7, 4, 2, 9, 6]
[2, 4, 7, 9, 6]
[2, 4, 7, 9, 6]
[2, 4, 6, 9, 7]
[2, 4, 6, 7, 9]
[2, 4, 6, 7, 9]

Related

How do I prevent python skipping an integer during an iteration after removing an integer from the list? [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 2 years ago.
I am attempting to remove duplicates from a list of numbers; however, the code doesn't remove all the duplicates. After debugging, I realized that an integer is skipped if the previous iteration results in the removal of that integer (neither of the 2's in the list are removed as they are preceded by 5 which was a duplicate):
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
for item in numbers_list:
if numbers_list.count(item) > 1:
numbers_list.remove(item)
else:
pass
print(numbers_list)
EDIT: I know there are other ways to remove duplicates from a list but I want to know how to ensure that iterations aren't skipped.
Just don't modify a list during iteration -- strange things happen, as you observed. Changing the list affects the iterator, and the results of that are, I think, implementation-defined. Doing it this way is considered bad style, exactly because of those weird effects. The easiest solution for your concrete problem of removing duplicates, which is equivalent to ensuring uniqueness, is to use a set:
unique_list = list(set(numbers_list))
You can leave out the list part if you are only interested in the result as an iterable. However, this (likely) won't preserve the orginal order in numbers_list -- if you need the order, some different approach is needed, like this:
def makeunique(l):
i = 0
while i < len(l):
j = i + 1
while j < len(l):
if l[j] == l[i]:
del l[j]
j += 1
i += 1
return l
(This has quadratic complexity, though.)
if you need to remove duplicates you can use this
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
list_no_dubl = list(set(numbers_list))
print(list_no_dubl)
If you want to remove duplicates you can do:
mylist = list(dict.fromkeys(mylist))
If you don't want to skip iterations you can use a while, for example:
counter = 0
while counter < len(mylist):
# if you want to remove an item, don't increase counter, otherwise yes
counter++
The function set() constructs a set object, which ensures that there are only unique, unordered collection of objects.
The function list() converts the set object back into a list.
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
deduped_list = list(set(numbers_list))
print(deduped_list)
>>[2, 3, 546, 5, 6, 7, 29]

How to remove first occurrence of a specific item from a list of items without using .pop() or .remove()

I have a list, let us call it l = [1,2,3,7,8,9,10,7]. Given this list l, I am trying to remove the first occurrence of the number 7 without using the .pop() or .remove() built-in functions.
I have tried
def remove_item(l, item_to_remove):
newlst = []
for item in l:
if item != item_to_remove:
newlst.append(item)
return newlst
However, this removes all instances of the item I am trying to remove when in fact I only want to remove the very first instance of that said specific item. Does anyone have some tips on how to accomplish this??
You only need to take care that the removing part of your code doesn't run twice.
lst = [1,2,3,7,8,9,10,7] # [1, 2, 3, 7, 8, 9, 10, 7]
print(lst)
for i in range(len(lst)):
if lst[i] == 7:
del lst[i] # [1, 2, 3, 8, 9, 10, 7]
break
print(lst)
It does exactly the same as the following:
lst = [1,2,3,7,8,9,10,7]
print(lst) # [1, 2, 3, 7, 8, 9, 10, 7]
for i in range(len(lst)):
if lst[i] == 7:
lst.pop(i)
break
print(lst) # [1, 2, 3, 8, 9, 10, 7]
as well as this
lst = [1,2,3,7,8,9,10,7]
print(lst) # [1, 2, 3, 7, 8, 9, 10, 7]
for i in range(len(lst)):
if lst[i] == 7:
lst.remove(lst[i])
break
print(lst) # [1, 2, 3, 8, 9, 10, 7]
Overview of the used methods:
del list[i] - The del statement can also be used to remove slices from a list
list.pop - remove and return item at index (default last). Raises IndexError if list is empty or index is out of range.
list.remove - remove first occurrence of value.Raises ValueError if the value is not present.
You just need to add a little logic to it. I add a looking variable which signifies that we havent found the entry were looking for. Heres the code
def remove_item(l, item_to_remove):
newlst = []
looking = True
for item in l:
if item != item_to_remove or not looking:
newlst.append(item)
else:
looking = False
return newlst
list = [1,3,4,5,6,7,3,10]
print(remove_item(list, 3))
which returns [1, 4, 5, 6, 7, 3, 10]
Very wasteful, but here you go, a solution:
def remove_first(sequence, element):
return sequence[:sequence.index(element)] + sequence[sequence.index(element)+1:]
Then you can:
>>> remove_first(["a", "b", "a", "c"], "a"):
['b', 'a', 'c']
index returns the index of the first found occurrence of an element.
The rest is sequence splicing and catenation.
Of course, you could generalize this to remove(sequence, element, n) to remove the n-th found element.
EDIT: I just stated falsely that index also supports that. Statement removed.
Or you could choose to mutate the input, but for one, returning the output is cleaner, and you could not have a general "sequence" argument, as not all sequences are mutable. See the tuple type.
.index(x) returns the first index location of x within the list, so just delete it. If x is not found, it returns ValueError.
my_list = [1, 2, 3, 7, 8, 9, 10, 7]
val = 7
if val in my_list:
del my_list[my_list.index(val)]
>>> my_list
[1, 2, 3, 8, 9, 10, 7]
Signature: my_list.index(value, start=0, stop=9223372036854775807, /)
Docstring:
Return first index of value.
Raises ValueError if the value is not present.
Welcome to StackOverflow!
Minor modification to your code,.
I would prefer remove but here is your modified code to do the required job
def remove_item(l, item_to_remove):
newlst = []
for item in l:
if item != item_to_remove:
newlst.append(item)
else:
return newlst + l[len(newlst) + 1 :]
return newlst
In Python, you can add the lists. Using list comprehensions, you select sub-lists(l[len(newlst) + 1 :]).
Testing
>>> list = [1,3,4,5,6,7,3,10]
>>> print(remove_item(list, 3))
[1, 4, 5, 6, 7, 3, 10]
lst = [1,2,3,7,8,9,10,7]
new_lst = lst[:lst.index(7)] + lst[lst.index(7) + 1:]
new_lst
[1, 2, 3, 8, 9, 10, 7]
Similar idea to CEWeinhauer's solution, but one which takes advantage of Python features to minimize overhead once we've found the item to remove:
def remove_item(l, item_to_remove):
newlst = []
liter = iter(l) # Make single pass iterator, producing each item once
for item in liter:
if item == item_to_remove: # Found single item to remove, we're done
break
newlst.append(item) # Not found yet
newlst += liter # Quickly consume all elements after removed item without tests
return newlst
The above works with any input iterable in a single pass, so it's better if the input might not be a list and/or might be huge. But it's admittedly more complex code. The much simpler solution is to just find the element with index and remove it. It might be slightly slower in some cases, since it's two O(n) steps instead of just one, but it uses C built-ins more, so it's likely to be faster in practice:
def remove_item(l, item_to_remove):
newlst = list(l)
del newlst[newlst.index(item_to_remove)]
return newlst

Longest sequence of consecutive duplicates in a python list

As mentioned, a run is a sequence of consecutive repeated values. Implement a Python function called longest_run that takes a list of numbers and returns the length of the longest run. For example in the sequence:
2, 7, 4, 4, 2, 5, 2, 5, 10, 12, 5, 5, 5, 5, 6, 20, 1 the longest run has length 4. Then, in the main, your program should ask the user to input the list, then it should call longest_run function, and print the result.
This is what I tried but it only returns 1 and I don't understand why. I can't import any modules for this question.
def longest_run(aList):
'''(list)->int
Returns length of the longest run
Precondition: aList is a list of a len of at least 2 and elements of list are ints
'''
count=0
bucket=[]
for i in aList:
if bucket==i:
count=count+1
else:
bucket=i
count=1
return count
The biggest mistake of your code is to set bucket=[] (which is a list) and later to an integer.
Also, you need to store the longest sequence and the current sequence length (initialized to 1) and the last seen value, so more variables than you're storing.
Each time value is the same as before, increase counter. If it's different, reset counter after having checked if it's not greater than the max. In the end perform the max test again just in case the longest sequence is in the end (classic mistake)
like this:
seq = [2, 7, 4, 4, 2, 5, 2, 5, 10, 12, 5, 5, 5, 5, 6, 20, 1]
result=1
max_result=0
last_seen=seq[0]
for v in seq[1:]:
if v==last_seen:
result += 1
else:
if result > max_result:
max_result = result
last_seen = v
result = 1
# just in case the longest sequence would be at the end of your list...
if result > max_result:
max_result = result
print(max_result)
When you're finally allowed to use python batteries, use itertools.groupby and compute the max of the sequence lengths:
max(sum(1 for x in v) for _,v in itertools.groupby(seq))
You get 1 since when your loop is in it's final iteration:
bucket = 20 and i = 1 which means bucket != i so the loop enters the else clause and assigns count = 1 exits and the function returns count which is 1.
Suggestions:
1) When you encounter a bug like this try running through the code/logic manually - it helps.
2) For this question specifically - whenever a run ends you forget the last run length, think about how you can "remember" the longest run.
So you're trying to find the longest run of the same number in your list? Because it's kinda confusing, what you were trying to code.
You should keep two versions of count: maxCount (the one which you're gonna return) and actualCount (the one you're gonna increment), iterate through the list and compare number with the next one. If it's the same actualCount += 1 if not, actualCount = 0 at the end of every iteration, compare maxCount and actualCount and if actualCount is bigger than maxCount = actualCount.
def longest_run(aList):
maxCount = 1
actualCount = 1
for i in range(len(aList)-1):
if aList[i] == aList[i+1]:
actualCount += 1
else:
actualCount = 1
if actualCount > maxCount:
maxCount = actualCount
return(maxCount)
You can use the following method:(Number of repetitions of each number)
mylist = [2, 7, 4, 4, 2, 5, 2, 5, 10, 12, 5, 5, 5, 5, 6, 20, 1]
my_dict = {i:mylist.count(i) for i in mylist}
mylist = list(dict.fromkeys(mylist))
R_list=[]
for i in mylist:
print("%s repeated %s" %(i,my_dict[i]))
R_list = R_list+[my_dict[i]]
print(R_list)
print(max(R_list))

Recursive function that takes in a list and an integer and returns the integer in the list in order

So let's say I this function:
squeeze([1,4,7,9], 8)
squeeze([1,4,7,9], 0)
I would want the function to return a new list containing:
[1,4,7,8,9]
[0,1,4,7,9]
I want to make this function using recursion, but I'm having trouble
def squeeze(x:list, num:int):
if len(x) == 1:
if num < x[0]:
return [num] + x #if the integer is less than the 1st value, put it in the front
elif x[0] < num < x[2]:
return [x[0]] + [num] + [x[2]] #put it in the list
#insert this number into the correct spot
else:
return squeeze(num, x[0:1]) + squeeze(num, x[1:]) #i don't think this is right
I'm having trouble comparing the numbers in the list and putting it in the correct spot by using recursion.
You can do like this
def squeeze(myList, num):
if myList == []:
return [num]
elif num > myList[0]:
return [myList[0]] + squeeze(myList[1:], num)
else:
return [num] + myList
print squeeze([1,4,7,9], 10)
print squeeze([1,4,7,9], 8)
print squeeze([1,4,7,9], 0)
Output
[1, 4, 7, 9, 10]
[1, 4, 7, 8, 9]
[0, 1, 4, 7, 9]
Explanation
If myList is empty, return the num as list
If the num is greater than the first element of the myList, num cannot fit here. So, we recurse further leaving the first element of the myList behind. (myList[1:] means that without 0th element). For example, myList is [4, 7, 9] and num is 8. So, we leave 4 behind and recurse with [7, 9] and 8, still 8 is greater than 7, so now we recurse with [9] this time, 8 is smaller than 9, so we return [8, 9] by the else part. And when the recursion unwinds, we get [7, 8, 9] and then finally [4, 7, 8, 9]
If the num is lesser than or equal to first element of myList, then that is where we need to place num. So, we simply place num at the beginning of myList and return it.
OK, there is already an answer upvoted and accepted BUT on the chance that this is not just a leaning/homework thing and someone actually wants to do this, there's a module for this.
check out what's available in bisect.
from bisect import insort
a = [1,4,7,9]
insort(a, 10)
print a
a = [1,4,7,9]
insort(a, 8)
print a
a = [1,4,7,9]
insort(a, 0)
print a
output is what is expected but there is also insort_left and insort_right to fine-tune your policy on ties.
Note: this does not return a new list, it modifies the old but is easily wrapped with a copy of the original first leaving you with a two line squeeze function.

Confusing behavior of Python for statement

I have the following python code:
x = range(0,10)
print x
for number in x:
print(number)
if number%2<> 0:
x.remove(number)
print x
Oddly, the out put is this:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0
1
3
5
7
9
[0, 2, 4, 6, 8]
The first and last lines are right, but why are 2,4,6, and 8 not printed out? The print statement is not inside the if statement!
I'm using python(x,y) on windows 7. Also, I'm new to Python...I'm used to C++
You're removing items from the list (x.remove) while iterating over it (for number in x).
for-in maintains an index separately, and that is why modifying the list gives unexpected behavior.
The list is iterated using its index, but when you remove elements you skip some indices.
E.g:
[0,1,2,...] # (iterator is at second - print 1)
remove
[0,2,3,...] # (iterator is still at second)
iterator advances
[0,2,3,...] # (iterator is at third - print 3)
Add some print statements for clarity:
x = range(10)
for index, number in enumerate(x):
print "x is ", x
print "element is", number
print "index is ", index
print
if number % 2 == 0:
x.remove(number)
And the output:
x is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
element is 0
index is 0
x is [1, 2, 3, 4, 5, 6, 7, 8, 9]
element is 2
index is 1
x is [1, 3, 4, 5, 6, 7, 8, 9]
element is 4
index is 2
x is [1, 3, 5, 6, 7, 8, 9]
element is 6
index is 3
x is [1, 3, 5, 7, 8, 9]
element is 8
index is 4
As you can see, index keeps going up by 1, even though you remove elements from the list. This is what causes the loop to skip elements.
As others have pointed out, looping over a list and removing elements from it isn't a good idea. Loop over a copy instead:
for number in x[:]:
Or:
for number in list(x):
Better yet, make a new list with a list comprehension:
[number for number in x if number % 2 == 0]
Basically you can have weird behavior when you iterate something while removing at the same time. What's happening is that you're skipping some values due to them being shifted to indexes that you already iterated over.
A better way of doing what you want (filter out some items), would be to use a list comprehension, for instance:
[x for x in range(10) if x%2==0]
You could simply use the range step to only create even numbers, but the above solution let's you filter out on any condition.
The reason why some numbers aren't printed is that the values are changing positions while you loop and remove them. When you remove the 1, you can imagine all the values being shifted by one position, the iterator is pointing to where the 2 used to be, but now the value there is 3, so the 2 is never printed. And this goes on for the rest of the values.
As Mark Rushakoff mentions, you shouldn't modify something while you're iterating over it. Change your for number in x to for number in x[:] and it will work as you expect, though. In this case you're iterating over a copy.
Don't modify a list you're iterating over. Others suggest copying the list or collecting a new list of things to remove. Instead, collect the ones you want to remain. This is faster than copying the list and removing from the copy not being iterated over, and faster than collecting the ones to remove and then removing them.
evens = []
for number in x:
if number%2 == 0:
evens += [number]
print(evens)

Categories

Resources