Building MIN-HEAP in Python - python

I've got to build a complete MIN-HEAP implementation in Python, without using built-in heap functions.
So I've got definitions of parent, left child and right child, which take in account that python number list elements from 0:
from random import randint
import math
def parent(i):
x = int(l.index(l[i])) #########3
y = int(math.floor(x/2))
return y-1
def lchild(i):
x = int(l.index(l[i]))
y = int(math.floor(x*2))
return y-1
def rchild(i):
x = int(l.index(l[i]))
y = int(math.floor(x*2 + 1))
return y-1
then I have a part of code that generates (pseudo)random list for me into the list l:
l = []
dl = int(input)
for i in range (0, dl):
x = int(randint(1,100))
l.append(x)
and until this point everything works good. then I have a function bkop for making the table l into a min-heap.
def bkop(l):
j = 0
for i in range(0, len(l)):
if int(l[i]) < int(parent(l[i])): #########2
l[i], l[parent(i)] = l[parent(i)], l[i]
j = j+1
if j != 0:
bkop(l)
then I want to run a program and see the results:
bkop(l) #########1
print l
The program crashes with an error list index out of range pointing to the 3 lines, that i've marked with #########. I've started writing this about a month ago and i'm pretty sure, that parent, lchild and rchild worked at that time. Do you know, what's wrong?
EDIT1:
Ok, so I've fixed the parent/lchild/rchild definitions. I've checked, they return correct values. Here is the code:
def parent(i):
x = i + 1
y = x//2
return y-1
def lchild(i):
x = i + 1
y = x*2
return y-1
def rchild(i):
x = i + 1
y = x*2 + 1
return y-1
The function generating random list is pretty much intact. Then I have this bkop function (that makes a min-heap out of the l list). I use print before and after on purpose, to se if it works... and it doesn't. It returns the same list both times, no compile errors or anything. Any idea how to fix it?
print(l)
def bkop(l):
j = 0
for i in range(0, len(l)):
if l[i] < parent(i):
l[i], l[parent(i)] = l[parent(i)], l[i]
j = j+1
if j != 0:
bkop(l)
bkop(l)
print l
EDIT2:
Ok, so I've fixed bkop as you've suggested:
print bkop(l)
def bkop(l):
j = 0
for i in range(1, len(l)):
if l[i] < l[parent(i)]:
l[i], l[parent(i)] = l[parent(i)], l[i]
j = j+1
if j != 0:
bkop(l)
bkop(l)
print bkop(l)
But when I run it, I get this first a randomly-generated table (as I should), and instead of a min-heap table, I get None value, as below:
[34, 9, 94, 69, 77, 33, 56]
None
Any ideas? Maybe I should do it another way around, not comparing l[i] to parent, but l[i] to it's left and right children?

So I've got the whole thing working with the help from Wombatz (Thank you very much!). Here is the working code for the future users:
from random import randint
def parent(i):
x = i + 1
y = x//2
return y-1
def lchild(i):
x = i + 1
y = x*2
return y-1
def rchild(i):
x = i + 1
y = x*2 + 1
return y-1
l = []
dl = int(input())
for i in range (0, dl):
x = int(randint(1,100))
l.append(x)
print l
def bkop(l):
j = 0
for i in range(1, len(l)):
if l[i] < l[parent(i)]:
l[i], l[parent(i)] = l[parent(i)], l[i]
j = j+1
if j != 0:
bkop(l)
bkop(l)
print l
When run, I've put 13 in input for the list length and i've got the result:
13
[30, 62, 9, 100, 75, 73, 57, 82, 2, 76, 2, 50, 41] #before heapify
[2, 2, 30, 62, 9, 41, 57, 100, 82, 76, 75, 73, 50] #after heapify

At first: there is a problem with your parent, lchild and rchild functions:
l[index] gets the value for a given index.
l.index(...) gets the index for a given value.
Your are trying to get the index for a value at a specific index. That's like x + 1 - 1. If your items are unique then that computed index will always be the same as the index you started with. When there are duplicates your functions will calculate the parent, left and right child of the first occurence of the value at that index.
That is probably not what you want. So set x to i or remove the x completely.
The actual problem is the following:
Your parent, lchild and rchild functions are desined to work on an index but you are passing the value of l at index i to the function: parent(l[i])
Since the values in the list may be much higher than the possible index range you are getting list index out of range. You probably want to pass the index directly.
Furthermore:
parent, lchild and rchild return incorrect values. Test these function without all the other stuff!
I assume the results should look like this:
parent(1) == parent(2) == 0
lchild(0) == 1
rchild(0) == 2
Your definition returns different values.
Some minor things:
All these random int casts are useless. Casting an int to int does nothing. The only line which really need the cast is: ``dl = int(input)`
int(math.floor(x/2)). Use integer division x // 2 instead
int(math.floor(x*2)). Integer times 2 is an integer. No need to floor it.
Edit
Two things are wrong with your bkop function.
l[i] < parent(i) You compare the value to an index. I think you want to campare the value at i to its parent value, not the parent index.
for i == 0 you compare the value at index 0 to the value at parent(0) == -1. This wraps around the list and is probably not what you want. As index 0 has no parent you shouldn't try to compare it to its parent.
Edit2
bkop does not return the list but modifies it in-place. Just print(l) at the end. You will see that the original list was modified.

Related

Implementing Merge Sort algorithm

def merge(arr,l,m,h):
lis = []
l1 = arr[l:m]
l2 = arr[m+1:h]
while((len(l1) and len(l2)) is not 0):
if l1[0]<=l2[0]:
x = l1.pop(0)
else:
x = l2.pop(0)
lis.append(x)
return lis
def merge_sort(arr,l,h): generating them
if l<h:
mid = (l+h)//2
merge_sort(arr,l,mid)
merge_sort(arr,mid+1,h)
arr = merge(arr,l,mid,h)
return arr
arr = [9,3,7,5,6,4,8,2]
print(merge_sort(arr,0,7))
Can anyone please enlighten where my approach is going wrong ?
I get only [6,4,8] as the answer. I'm trying to understand the algo and implement the logic my own way. Please help.
Several issues:
As you consider h to be the last index of the sublist, then realise that when slicing a list, the second index is the one following the intended range. So change this:
Wrong
Right
l1 = arr[l:m]
l1 = arr[l:m+1]
l2 = arr[m+1:h]
l2 = arr[m+1:h+1]
As merge returns the result for a sub list, you should not assign it to arr. arr is supposed to be the total list, so you should only replace a part of it:
arr[l:h+1] = merge(arr,l,mid,h)
As the while loop requires that both lists are not empty, you should still consider the case where after the loop one of the lists is still not empty: its elements should be added to the merged result. So replace the return statement to this:
return lis + l1 + l2
It is not advised to compare integers with is or is not, which you do in the while condition. In fact that condition can be simplified to this:
while l1 and l2:
With these changes (and correct indentation) it will work.
Further remarks:
This implementation is not efficient. pop(0) has a O(n) time complexity. Use indexes that you update during the loop, instead of really extracting the values out the lists.
It is more pythonic to let h and m be the indices after the range that they close, instead of them being the indices of the last elements within the range they close. So if you go that way, then some of the above points will be resolved differently.
Corrected implementation
Here is your code adapted using all of the above remarks:
def merge(arr, l, m, h):
lis = []
i = l
j = m
while i < m and j < h:
if arr[i] <= arr[j]:
x = arr[i]
i += 1
else:
x = arr[j]
j += 1
lis.append(x)
return lis + arr[i:m] + arr[j:h]
def merge_sort(arr, l, h):
if l < h - 1:
mid = (l + h) // 2
merge_sort(arr, l, mid)
merge_sort(arr, mid, h)
arr[l:h] = merge(arr, l, mid, h)
return arr
arr = [9, 3, 7, 5, 6, 4, 8, 2]
print(merge_sort(arr,0,len(arr)))

(python) Can you please tell me what is the problem in the code below

I just start to learn python and i have a problem:
arr = [1,3,3,3,0,1,1]
def solution(arr):
a=[]
for r in range(len(arr)-1):
if arr[r] == arr[r+1]:
a.append(r+1)
print(a)
for i in range(len(a)):
k = int(a[i])
arr[k] = -1
arr.remove(-1)
return arr
There's a message
IndexError: list index out of range for ''arr[k] = -1''
Can you please tell me the reason for the Error and correct it?
Of course, it results in a Runtime exception. The list a stores indices. For each element v in a, you are trying to remove the value arr[v]. Doing this will reduce the size of arr by one every time. So, in the next iteration, v can be greater than the size of arr. Hence, it results in List index out of bound exception.
Your code, corrected:
arr = [1,3,3,3,0,1,1]
def solution(arr):
a=[]
for r in range(len(arr)-1):
if arr[r] == arr[r+1]:
a.append(r+1)
print(a)
c = 0
for i in range(len(a)):
k = int(a[i])
arr[k - c] = -1
arr.remove(-1)
c += 1
return arr
print(solution(arr))
It looks like you are trying to remove consecutive duplicates from the list. This can be easily solved using the following code.
def remove_duplicates(arr):
stack = [arr[0]]
for i in range(1, len(arr)):
if stack[-1] != arr[i]:
stack.append(arr[i])
return stack
print(remove_duplicates([1,3,3,3,0,1,1]))
In short, you cannot modify the array shape when you have determined the indices based on the unmodified array to index into it.
Here is something that you might be looking for:
def solution(arr):
a = []
for r in range(len(arr) - 1):
if arr[r] == arr[r + 1]:
a.append(r + 1)
print(a)
for i in range(len(a)):
k = int(a[i])
arr[k] = -1
# In the following line, you cannot modify the array length
# when you have already computed the indices based on the unmodified array
# arr.remove(-1)
arr = [x for x in arr if x != -1] # This is a better way to deal with it
return arr
print(solution(arr=[1, 3, 3, 3, 0, 1, 1]))
You don’t want to mess with the original list. Otherwise you’ll run into index errors. Index errors mean the item you were looking for in the list no longer exists. Most likely this line was the culprit arr.remove(-1).
arr = [1,3,3,3,0,1,1]
solution = []
for i, v in enumerate(arr):
if i == 0 or v != arr[i -1]:
solution.append(v)
print(solution)
This should get you what you are after. enumerate tells you want index you are at when looping through the list. More information can be found here: https://realpython.com/python-enumerate/
Well, you've probably already know what wrong happened here, removing the element inside the loop:
for i in range(len(a)):
k = int(a[i])
arr[k] = -1
arr.remove(-1)
You can fix the whole thing just changing the line to this list filter+lambda implementation, well, not inside the loop, but after the completion of loop iterations, just like follows:
for i in range(len(a)):
k = int(a[i])
arr[k] = -1
arr = list(filter(lambda x: x != -1, arr))
And you'll get what you want just from your solution!

Finding if the next element is smaller than the one before it and deleting it from the list python

I am having trouble with my code, I am writing a method that will check if the next element is smaller than the previous element and if it is, it will delete it.
Example:
Input: [1, 20, 10, 30]
Desired output: [1,20,30]
Actual output: [30]
def findSmaller(s):
i = -1
y = []
while i <= len(s):
for j in range(len(s)):
if s[i+1] <= s[i]:
del s[i + 1]
y.append(s[i])
i += 1
return y
If you are uncertain about how your loops work I recommend adding in some print statements. That way you can see what your loop is actually doing, especially in more complicated problems this is useful.
Something like this would solve your problem.
a = [1,2,3,2,4]
for k in range(0,len(a)-2): #-2 so that one don't go past the loops length
#print(k)
y = a
if(a[k]>a[k+1]):
del y[k+1] #delete the k+1 element if it is
>>> s = [5, 20, 10, 15, 30]
>>> max_so_far = s[0]
>>> result = []
>>> for x in s:
if x >= max_so_far:
result.append(x)
max_so_far = x
>>> result
[5, 20, 30]
Depending whether you need to do some calculation later with the list you can use a generator
s = [1, 20, 10, 30]
def find_smaller_generator(l: list):
last_item = None
for item in l:
if last_item is None or item >= last_item:
last_item = item
yield item
def find_smaller_list(l: list):
return list(find_smaller_generator(l))
print(find_smaller_list(s))
for i in find_smaller_generator(s):
print(i)
print([i**2 for i in find_smaller_generator(s)])
this returns:
[1, 20, 30]
1
20
30
[1, 400, 900]
You can try something like this
def findSmaller(s):
# sets p (previous) as the first value in s
p = s[0]
# initializes y to be an array and sets the first value to p
y = [p]
# iterates over s, starting with the second element
for i in s[1::]:
# checks if i is greater than or equal to the previous element
if i >= p:
# if it is, i is appended to the list y
y.append(i)
# also set the previous value to i, so the next iteration can check against p
p = i
#returns the list
return y
What this does is iterate over s and checks if the current item in the list is greater than or equal to the previous element in the list. If it is then it appends it to y, and y is returned.
Try out the code here.

How write a function that sorts and appends to new list?

I was trying to write some code that will run through a list, unsorted, find the lowest number, than move it to result. It will do this until unsorted becomes empty. Basically, a sort program.
Did I correctly put the findMin function in a loop in the electionSort function?
How do I take a return value and append it to a new list?
unsorted = [4, -9, 10, 2, 3.7, -20, 0]
result = []
def findMin(l):
l = unsorted
if len(l) == 0:
return None
min = l[0]
for i in range (len(l)):
if l[i] < min:
min = l[i]
return min
def selectionSort():
for a in range (len(unsorted)):
findMin(l)
result.append(min)
print(result)
selectionSort()
If you are finding the min each time and putting it in a new list, you need to remove that min from the original list before finding another minimum. remove function on a list remove's by value.
def selectionSort():
for a in range (len(unsorted)):
min = findMin(unsorted)
result.append(min)
unsorted.remove(min)
print(result)
Output:
[-20, -9, 0, 2, 3.7, 4, 10]
you just need to pass it to append function but you need to pass the list as argument to selectionSort function too :
def selectionSort(l):
for a in range (len(l)):
result.append(findMin(l))
print(result)
but still your functions have many problems , you can use this to recipe as a more pythonic way for implement the selectionsort :
Recursive Selection Sort :
def sel_sort_rec(seq, i):
if i==0: return
max_j = i
for j in range(i):
if seq[j] > seq[max_j]: max_j = j
seq[i], seq[max_j] = seq[max_j], seq[i]
sel_sort_rec(seq, i-1)
selectionsort :
def sel_sort(seq):
for i in range(len(seq)-1,0,-1):
max_j = i
for j in range(i):
if seq[j] > seq[max_j]: max_j = j
seq[i], seq[max_j] = seq[max_j], seq[i]

How to write this program into a for loop?

I'm trying to learn how to change this program into a for loop for the sake of knowing both ways
def Diff(a_list):
num = enumerate(max(x) - min(x) for x in a_list)
return max(x[::-1] for x in num)
I want it to be something like
def Diff(x):
for a in x
if it helps the program is intended to return the row that has the smallest sum of the elements inside it so like [[1,2,3,4],[-500],[10,20]] would be 1.
I do not understand why you use this name for your function, it does something else (as far as I understand). It searches for the inner-list inside a list for which the difference between min and max, the span, are maximal and the n returns a tuple (span, idx), idx being the index within the outer loop.
When you want to have the same as a loop, try:
def minRow_loop(a_list):
rv = (0,0)
for idx, row in enumerate(a_list):
span = max(row) - min(row)
span_and_idx = (span, idx)
if span_and_idx > rv:
rv = span_and_idx
return rv
But your code doesn't do what it'S intended to do, so I created two correct versions, once with and once without a loop.
import random
random.seed(12346)
def minRow(a_list):
num = enumerate(max(x) - min(x) for x in a_list)
return max(x[::-1] for x in num)
def minRow_loop(a_list):
rv = (0,0)
for idx, row in enumerate(a_list):
span = max(row) - min(row)
span_and_idx = (span, idx)
if span_and_idx > rv:
rv = span_and_idx
return rv
def minRow_correct(a_list):
return min(enumerate([sum(l) for l in a_list]),
key=lambda (idx, val): val)[0]
def minRow_correct_loop(a_list):
min_idx = 0
min_sum = 10e50
for idx, list_ in enumerate(a_list):
sum_ = sum(list_)
if sum_<min_sum:
min_idx = idx
min_sum = sum
return min_idx
li = [[random.random() for i in range(2)] for j in range(3)]
from pprint import pprint
print "Input:"
pprint(li)
print "\nWrong versions"
print minRow(li)
print minRow_loop(li)
which prints:
Input:
[[0.46318380478657073, 0.7396007585882016],
[0.38778699106140135, 0.7078233515518557],
[0.7453097328344933, 0.23853757442660117]]
Wrong versions
(0.5067721584078921, 2)
(0.5067721584078921, 2)
Corrected versions
2
2
What you want can actually be done in two lines of code:
# Let's take the list from your example
lst = [[1,2,3,4],[-500],[10,20]]
# Create a new list holding the sums of each sublist using a list comprehension
sums = [sum(sublst) for sublst in lst]
# Get the index of the smallest element
sums.index(min(sums)) # Returns: 1
if you're looking for minimum sum, just go through every row and keep track of the smallest:
def minRow(theList):
foundIndex = 0 # assume first element is the answer for now.
minimumSum = sum(theList[0])
for index, row in enumerate(theList):
if sum(row) < minimumSum:
foundIndex = index
minimumSum = sum(row) # you don't have to sum() twice, but it looks cleaner
return foundIndex
If your looking for greatest range (like the first Diff() function), it'd be similar. You'd keep track of the greatest range and return its index.
Thorsten's answer is very complete. But since I finished this anyway, I'm submitting my "dumbed down" version in case it helps you understand.

Categories

Resources