Hoare partitioning falls into infinite loop - python

I am trying to write a Hoare partitioning function that takes an array as input, and partitions it with the first element as pivot (I know it's not a good idea, I should be using randomized pivots, like the median-of-medians approach). Problem is that this function falls into infinite loop when the first element is the highest, as with the array [14,6,8,1,4,9,2,1,7,10,5]. I can see the error, after the first iteration of the outer while, both i and j equal 10, and hence the loop continues forever. Which portion should I mend to get the desired effect? Here's the code:
def hoare(arr):
pivot = arr[0]
i,j = 1,len(arr)-1
while i <= j:
while i < j and arr[i] < pivot:
i += 1
while j >= i and arr[j] >= pivot:
j -= 1
if i < j:
arr[i],arr[j] = arr[j],arr[i]
if j != 0:
arr[0],arr[j] = arr[j],arr[0]
return j

I believe the problem is that you've converted a do-while (or repeat-until, in Hoare's terms) loop into a while loop, so it never does the first j -= 1.
The simplest transformation in Python should be to change the two inner while loops like this:
while True:
i += 1
if not (i < j and arr[i] < pivot): break
while True:
j -= 1
if not (j >= i and arr[j] >= pivot): break
(I'm assuming here that the if i < j: is supposed to be outside the second while loop, and all of the other initial indentation is correct.)
I haven't reasoned this through completely, or run a variety of tests, but there's probably more than just this one error in your translation. You may need to also convert the outer loop into a do-while (Hoare actually makes it an explicit while TRUE with a check at the end), but I'm not sure. Anyway, for your sample input, the modified version returns 9, and arr is [10, 6, 8, 1, 4, 9, 2, 1, 7, 14, 5], which is incorrect, but it solves your infinite loop problem.
The next problem is an off-by-one error. If you're going to do the += 1 and -= 1 first in the inner loops, you have to start at -1, len(arr) rather than 0, len(arr)-1 (or, as you did, 1, len(arr)-1).
There may still be other problems. But I don't want to dig through your code finding all possible mistakes and explaining them. If you need that, tell us what our source was, and explain each transformation you made from that source, and it'll be much easier to explain where you went wrong. If not, it's much simpler to just translate Hoare's algorithm to Python directly, and then hopefully you can figure it out.
Here's a copy of the Hoare pseudocode that I found online (just replacing all tabs with two spaces):
Hoare-Partition (A, p, r)
x ← A[p]
i ← p − 1
j ← r + 1
while TRUE
repeat j ← j − 1
until A[j] ≤ x
repeat i ← i + 1
until A[i] ≥ x
if i < j
exchange A[i] ↔ A[j]
else
return j
Here's a trivial translation into Python; the only changes are minor syntax (including the way "exchange" is spelled) and turning each repeat/until into a while True/break.
def hoare(a, p, r):
x = a[p]
i, j = p-1, r+1
while True:
while True:
j -= 1
if a[j] <= x:
break
while True:
i += 1
if a[i] >= x:
break
if i < j:
a[i], a[j] = a[j], a[i]
else:
return j
For a function with the same signature as yours:
def hoare0(arr):
return hoare(arr, 0, len(arr)-1)

There is an error in this line:
while i < j and arr[i] < pivot:
It should be:
while i <= j and arr[i] < pivot:
The whole code for partition looks like:
def partition(a, l, r):
pivot = a[r]
i = l - 1
j = r
while i <= j:
if i <= j and a[i] < pivot:
i += 1
if i <= j and a[j] >= pivot:
j -= 1
if i < j:
a[i], a[j] = a[j], a[i]
a[l], a[j] = a[j], a[l]
return j
Why there was an infinite loop?
The pivot chosen here is 14.
So, after this code is executed:
while i < j and arr[i] < pivot:
i += 1
i is 10 and j is 10.
Now, when this block is executed:
while i <= j and arr[j] >= pivot:
j -= 1
As a[10] < 14, nothing happens. Since, i equals j, no swap happens. Now, since the outermost loop has condition i <= j, the loop keeps repeating.
What happens with correction?
So, after this code is executed:
while i <= j and arr[i] < pivot:
i += 1
i is 11 (because the condition is still true when i equals j) and j is 10.
Now, when this block is executed:
while i <= j and arr[j] >= pivot:
j -= 1
As a[10] < 14, nothing happens.
Now, i is 11 and j is 10, so no swap happens. But, the outermost loop is broken and a[j] swaps with pivot.
Your array becomes:
[5, 6, 8, 1, 4, 9, 2, 1, 7, 10, 14]
You can play here. It contains code with debug prints for both right and wrong partition schemes.

This Also Works :
key = arr[0]
i = 0
j = n-1
while i >= j:
while arr[i] < key:
i += 1
while arr[j] > key:
j -= 1
arr[j], arr[0] = arr[0], arr[j]

Partition algorithm has many variants, (short/long step), but we should be very careful with invariants,preconditions and non-structured programming statements (break, return ) concerning this classic algorithm.
Otherwise, we may fall in big troubles. Even when this can be against 'pythonic' philosophy of coding.
The next annotated solution (for didactic purposes) yields (10, [5, 6, 8, 1, 4, 9, 2, 1, 7, 10, 14]) for the original list [14,6,8,1,4,9,2,1,7,10,5], as expected. Comments can be stripped off,
def hoare(arr):
# P: len(arr) > 0
assert len(arr)>0
i,j = 1,len(arr)
# INV : \forall n : 1<=n<i :arr[n]<arr[0]
# \forall n : j<=n<len(arr) :arr[n]>=arr[0]
# Quote(j-i)>=0
while i < j:
aa,bb=i,j
while aa < j and arr[aa] < arr[0]:
aa += 1
while bb > aa and arr[bb-1] >= arr[0]:
bb -= 1
#let
# aa = min n : i<=n<=j: n<j -> arr[n]>=arr[0]
# bb = max n : aa<=n<=j: n>aa -> arr[n-1]<arr[0]
#in
if (bb-aa)==(j-i):
#restore
arr[i],arr[j-1] = arr[j-1],arr[i]
#step
i, j = i+1 , j -1
else:
#restore
pass
#step
i,j = aa,bb
arr[0],arr[j-1] = arr[j-1],arr[0]
return j-1,arr
# Q : \forall n : 0<=n<j-1 :arr[n]<arr[j]
# \forall n : j-1<=n<len(arr) :arr[n]>=arr[j]
EDIT:
I'm not against goto, breaks, and continues... Donald Knuth stresses that "structured programming" is an idea rather than a language...
Once you understand the laws, you can break them... is this more pythonic? Invariant keeps and quote decreases every loop.
def hoare_non_str(arr):
assert len(arr)>0
i,j = 1,len(arr)
while i < j:
while i < j and arr[i] < arr[0]:
i += 1
if i==j:
break
while j > i and arr[j-1] >= arr[0]:
j -= 1
if i==j:
break
#It is safe to swap here.
arr[i],arr[j-1] = arr[j-1],arr[i]
i = i + 1
# swap pivote
arr[0],arr[j-1] = arr[j-1],arr[0]
return j-1,arr

Related

Problem in merge sort. After running , it runs forever. I am not able to find out the mistake in the code

the code of my merge sort runs forever. I am not able to find out the mistake.
Have I missed the base condition to stop the code? If it is so still I am not able to find out the mistake.
def merge(arr, l, mid, r):
n1=mid-l+1
n2=r-mid
leftarr=[None]*(n1)
rightarr=[None]*(n2)
for i in range(n1):
leftarr[i]=arr[l+i]
for j in range(n2):
rightarr[j]=arr[mid+j+1]
i=0
j=0
k=0
while i<n1 and j<n2:
if leftarr[i]< rightarr[j]:
arr[k]=leftarr[i]
i+=1
elif rightarr[j] < leftarr[i]:
arr[k]=rightarr[j]
j+=1
k+=1
while i<n1:
arr[k] = leftarr[i]
i += 1
k+=1
while j<n2:
arr[k]= rightarr[j]
j+=1
k+=1
def mergeSort(arr, l, r):
if l<r:
mid = (l+r)//2
mergeSort(arr,l,mid)
mergeSort(arr,mid+1,r)
merge(arr,l,mid,r)
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
print("Given array is")
for i in range(n):
print("%d" % arr[i]),
mergeSort(arr, 0, n - 1)
print("\n\nSorted array is")
for i in range(n):
print("%d" % arr[i]),
In output I only get the input array and the sorted array is never printed.
The code runs infinitely.
This is the image of the output
If you encounter a bug, you should debug it manually or with a debugging tool, etc.
So, for your problem, we can insert a print function for debugging in the mergeSort function.
For example, print the arr when l equals 3 and r equals 4.
def mergeSort(arr, l, r):
if l<r:
mid = (l+r)//2
mergeSort(arr,l,mid)
mergeSort(arr,mid+1,r)
merge(arr,l,mid,r)
if l == 3 and r == 4:
print(arr, l, r)
exit()
Then the output is [5, 6, 13, 5, 6, 7], which is not intended obviously.
Keep doing like so, print the arr in the middle of mergeSort function, you'll notice that you assign the array incorrectly in the 20th line.
In consequence, when the last merge happens (when l equals 0 and r equals 5), the while in the 18th line will loop infinitely, because the leftarr[0] equals rightarr[0], which prevents i or j get increased.
There are multiple problems in your code:
k should be initialized to l instead of 0. As coded, the merge loop corrupts the beginning of the array.
the test elif rightarr[j] < leftarr[i]: is useless and incorrect. else: suffices and the element from the right half will be copied. The test causes the loop to run forever if the next element from the left array and the right array are identical as neither i nor j are incremented. The array does not contain duplicate values, but the previous bug causes some numbers to be copied in the wrong place and hence may produce duplicates.
Note also that the initialization of leftarr and rightarr can be performed in a much more compact and efficient way: leftarr = arr[l:mid+1] and rightarr = arr[mid+1:r+1]
Here is a modified version:
def merge(arr, l, mid, r):
leftarr = arr[l : mid+1]
rightarr = arr[mid+1 : r+1]
i = 0
j = 0
k = l
while i < len(leftarr) and j < len(rightarr):
if leftarr[i] <= rightarr[j]:
arr[k] = leftarr[i]
i += 1
else:
arr[k] = rightarr[j]
j += 1
k += 1
while i < len(leftarr):
arr[k] = leftarr[i]
i += 1
k += 1
while j < len(rightarr):
arr[k] = rightarr[j]
j += 1
k += 1
def mergeSort(arr, l, r):
if l < r:
mid = (l + r) // 2
mergeSort(arr, l, mid)
mergeSort(arr, mid + 1, r)
merge(arr, l, mid, r)
arr = [12, 11, 13, 5, 6, 7]
n = len(arr)
print("Given array is")
for i in range(n):
print("%d" % arr[i])
mergeSort(arr, 0, n - 1)
print("\n\nSorted array is")
for i in range(n):
print("%d" % arr[i])
Note these remarks:
Using r as the index to the last element and mid the index to the last element of the left half causes many confusing +1/-1 adjusments. A different convention is more idiomatic in Python, where r is the index beyond the end of the last element and mid the first element of the right half.
l looks confusingly similar to 1, which makes it a poor choice of variable name.
saving the right half of the array slice is not necessary as its elements are copied before they may be overwritten.
Here is a simplified version:
def merge(arr, lo, mid, hi):
leftarr = arr[lo : mid]
i = 0
j = mid
k = lo
while i < len(leftarr):
if j >= hi or leftarr[i] <= arr[j]:
arr[k] = leftarr[i]
i += 1
else:
arr[k] = arr[j]
j += 1
k += 1
def mergeSort(arr, l, r):
if r - l >= 2:
mid = (l + r) // 2
mergeSort(arr, l, mid)
mergeSort(arr, mid, r)
merge(arr, l, mid, r)
arr = [12, 11, 13, 5, 6, 7]
print("Given array is")
for v in arr:
print("%d" % v)
mergeSort(arr, 0, len(arr))
print("\n\nSorted array is")
for v in arr:
print("%d" % v)

Leetcode 3Sum: why is result duplicated?

I am trying the 15. 3Sum code challenge on LeetCode:
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Here is my attempt:
class Solution(object):
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) < 3:
return []
nums.sort()
ret_val = []
for i in range(len(nums) - 2):
if i - 1 >= 0 and nums[i - 1] == nums[i]:
while nums[i - 1] == nums[i] and i < len(nums) - 2:
i += 1
if nums[i - 1] == nums[i]:
break
j = i + 1
k = len(nums) - 1
# This is our target sum
target = -nums[i]
while j < k:
if j - 1 != i and nums[j - 1] == nums[j]:
while nums[j - 1] == nums[j] and j < k:
j += 1
elif k + 1 != len(nums) and nums[k + 1] == nums[k]:
while nums[k + 1] == nums[k] and j < k:
k -= 1
else:
if nums[j] + nums[k] == target:
ret_val.append([nums[i], nums[j], nums[k]])
j += 1
k -= 1
elif nums[j] + nums[k] < target:
j += 1
else:
k -= 1
return ret_val
There is apparently a bug in my code that I couldn't figure out, even after running the Python Debugger. My code gives the wrong result when I use the input of [-11, 1, -12, -12, 10]. It creates an unnecessary duplicate in the answer. I've never seen this happen before, but Python seems to run the for loop one too many times.
The expected output should be [[-11, 1, 10]], but it instead gives [[-11, 1, 10], [-11, 1, 10]]. Another interesting thing I discovered is that by removing either one or both instances of -12, it ends up giving the correct answer of [[-11, 1, 10]].
What is the problem in my code?
The reason is that your outer loop will sometimes increment i (the loop variable). This is not good, because that incremented i value is scheduled to be i's value in a next iteration of that loop, and so you will have two iterations happening with the same value for i.
Note how Python's for i in range iteration does not take into account any increment you apply to i in one of the iterations. The full range of values will be iterated, no matter what you do with i in the mean time. This is different from more traditional for loop constructs that you find in other programming languages:
for (int i = 0; i < len(nums) - 2; i++)
...as in that case the loop's i++ will just act on whatever value i has at that moment, taking into account any change you made to i during an iteration. Again, this is not how it works with an iterator such as Python's range function returns.
The solution is to just exit the current iteration when you would have wanted to increment i, since you know that the next iteration will have that incremented value for i anyhow.
So change this piece of code:
if i - 1 >= 0 and nums[i - 1] == nums[i]:
while nums[i - 1] == nums[i] and i < len(nums) - 2:
i += 1
if nums[i - 1] == nums[i]:
break
to this:
if i - 1 >= 0 and nums[i - 1] == nums[i]:
continue
That will solve the issue.

Why doesn't the following heapsort function produce an error

I took the following code from GeeksforGeeks to try and understand heap sort
def heapify(arr, n, i):
largest = i
l = 2*i + 1
r = 2*i + 2
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
if largest != i:
arr[i],arr[largest] = arr[largest],arr[i]
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
for i in range(n, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
arr = [7, 11, 13, 6, 5, 12]
heapSort(arr)
print ("Sorted array is", arr)
On the very first iteration,
n = 6 and l = 13
Then for the following line of code
if l < n and arr[i] < arr[l]
arr[l] points to an index that doesn't exist.
I don't understand why this doesn't flag an error like "out of index" or something. Even though its an "if" statement, it is still surely checking the value in arr[l]. As this doesn't exist, it should "break" and flag an error?
Thanks
if-statement conditions are evaluated in the order that they are defined. they are also optimized.
if l < n and arr[i] < arr[l]
The l < n will be evaluated first. It's False. Since anding anything with False will be false anyway, the arr[i] < arr[l] is never evaluated. Hence you never get the IndexError

QuickSort in Python. Increment in array trouble

I am trying to implement quicksort in python. Problem is how to increment/decrement value of i/j in array a. I know that I should write i=i+1 and there are no such thing like i++ in python, but I don't understand in which way I should do this.
I am newbie, here is my code.
def quicksort(a,lo,hi):
if(hi<=lo):
return
i = lo - 1
j = hi
v = a[hi]
while True:
while(a[++i] < v):
pass
while(v < a[--j]):
if(j==lo):
break
if(i>=j):
break
t = a[i]
a[i] = a[j]
a[j] = t
t = a[i]
a[i] = a[hi]
a[hi] = t
quicksort(a, lo, i - 1)
quicksort(a, i + 1, hi)
in python, you cannot assign and get the value, that's an intentional limitation to avoid issues with typos, find the proper sequence point...
You have no choice than to "emulate" the C-ported code:
while(a[++i] < v):
pass
while(v < a[--j]):
if(j==lo):
break
(note that both constructs generate an infinite loop because:
++i == i
and
--j == j
(applying unary plus any number of times or unary minus an even number of times gives the same number, see Why Don't Two Plus Operators Throw an Error (e.g., 1 + + 2))
So change to:
i += 1
while(a[i] < v):
i += 1
j -= 1
while(v < a[j]):
if(j==lo):
break
j -= 1
The following construct does not work the same way in Python as it does in C++:
while(a[++i] < v):
as well as this one:
while(v < a[--j]):
The way you change the code is the following:
def quicksort(a,lo,hi):
if(hi<=lo):
return
i = lo - 1
j = hi
v = a[hi]
while True:
i += 1
while(a[i] < v):
i += 1
pass
j -= 1
while(v < a[j]):
j -= 1
if(j==lo):
break
if(i>=j):
break
t = a[i]
a[i] = a[j]
a[j] = t
t = a[i]
a[i] = a[hi]
a[hi] = t
quicksort(a, lo, i - 1)
quicksort(a, i + 1, hi)

Merge Sort Problems - Python

What's wrong with my code? It prints only a part of the vect values. Seems that the while loop breaks at some point. I don't understand why.
def print_list(vect):
for i in range(0, len(vect)):
print(vect[i])
def merge_sort(vect):
left = []
right = []
result = []
for i in range(0, int(len(vect)/2)):
left.append(vect[i])
for i in range(int(len(vect)/2), len(vect)):
right.append(vect[i])
left.sort()
right.sort()
i = 0
j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
print(len(result))
return result
vect = [3, 1, 5, 7, 10, 2, 0]
vect = merge_sort(vect)
Well, your mistake is that after your while loop
while i < len(left) and j < len(right):
...
it may be (and most probably would) that either i < len(left) or j < len(right), so you need to append appropriate part's suffix to the answer. It's easy to do with
result += left[i:]
result += right[j:]
Explanation:
Imagine the merge procedure: you have i and j at 0 at start, and at every step you move one of them forward. When you stop ? When one of them reaches the end. Let's say i has reached the end. Hereby you added the whole left part to the result, but there are still some elements in right between j and len(right), so you have to add them to the answer too.
Offtop:
You are implementing merge sort, so please have
left = merge_sort( left )
right = merge_sort( right )
instead of
left.sort()
right.sort()
Attention: you have to add the following check to your code at the beginning of merge function to avoid infinite recursion:
if len( vect ) == 1:
return vect
Also in your print_list function you can just use
print vect
or at least
for x in vect
print x
The while loop exits as soon as either left or right is exhausted. This leaves all the elements left in the unexhausted list unmerged.
Change your code to this:
def print_list(vect):
for i in range(0, len(vect)):
print(vect[i])
def merge_sort(vect):
left = []
right = []
result = []
for i in range(0, int(len(vect)/2)):
left.append(vect[i])
for i in range(int(len(vect)/2), len(vect)):
right.append(vect[i])
left.sort();
right.sort();
i = 0
j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
if i < len(left):
result.extend(left[i:])
else:
result.extend(right[j:])
print(len(result))
return result
vect = [3, 1, 5, 7, 10, 2, 0]
vect = merge_sort(vect)
print vect
If you're using the sort method on left and right, I'm a little confused as to why you're not just using it on the list as a whole. But I suppose you have your reasons. :)
Edit: I put the entire block of code there so you can see it. When I run this, it prints [0, 1, 2, 3, 5, 7, 10], which is correct.

Categories

Resources