python heapsort implementation - python

I am trying to implement heapsort algorithm in Python.
I get the error: list index out of range, although this part of the code should not be executed if the index is out of range.
def swaper(child,parent,a):
temp = a[parent]
a[parent]=a[child]
a[child]=temp
def digswap(swap,a):
'''
swap here is the position of the former child, which was just swapped with
its parent. The concept is to check if the node that now contains the parent value
has childs. If it has, then we might have to restore the heap property.
'''
if (2*swap)<=len(a):
if a[2*swap]>a[swap]:
swaper(2*swap, swap, a)
digswap(2*swap,a)
if (2*swap+1)<=len(a):
if a[2*swap+1]>a[swap]:
swaper(2*swap+1, swap, a)
digswap(2*swap+1,a)
I get the "list index out of range value" for "if a[2*swap]>a[swap]". I don't understand why, since this part should not be executed if 2*swap > lean(a).

Lists are 0-indexed. If 2*swap == len(a), then the last valid index in a is 2*swap - 1, hence your error.
As an aside, you don't need the swapper function; you can simply write a[parent], a[child] = a[child], a[parent]. It's much more efficient and is a common Python idiom.

Array indexing starts at 0. This leads you to access one past the last element of the array.
say you have a = [1,2,3,4] then len(a) is 4. The last element of this array is a[3]. This means that from the line:
if (2*swap)<=len(a):
you can get a value of up to 2 for swap which means that you essentially are doing:
a[swap*2]
a[4]
which is one past the end of the array.

Related

Finding the middle of a list

So I am trying to find the median of the list "revenues" which will be named "base_revenue".
Comments:
#Assume that revenues always has an odd number of members
#Write code to set base_revenue = midpoint of the revenue list
#Hint: Use the int and len functions
revenues = [280.00, 382.50, 500.00, 632.50, 780.00]
{def findMiddle(revenues):
middle = float(len(revenues))/2
if middle % 2 != 0:
return revenues[int(middle - .5)]
else:
return (revenues[int(middle)], revenues[int(middle-1)])}
I'm getting invalid syntax. The median function itself works, but maybe there is a more efficient way to do it.
Hint: the answer to this is far simpler than you've made it. You can even do it in a single line, unless your instructor specifically requires you to define a function.
You're told the list will always have an odd number of items; all you need is the index of the middle item. Remember that in Python, indices start at 0. So, for instance, a list of length 5 will have its middle element at index 2. A list of length 7 will have its middle element at index 3. Notice a pattern?
Your assignment also reminds you about len(), which finds the length of something (such as a list), and int(), which turns things (if possible) into integers. Notably, it turns a floating-point number into the the closest integer at or below it (a "floor" function); for instance it turns 2.5 into 2.
Can you see how you might put those together to programmatically find the midpoint index?

Return the First and Last Elements in a List Python, why does this method not work?

I'm supposed to create a function that takes a list of elements and return the first and last elements as a new list.
def first_last(lst)
return lst[0, -1]
TypeError: list indices must be integers, not tuple
Why does this not work? I looked at the answer. It's
def first_last(lst):
return [lst[0], lst[-1]]
I don't get it, can someone explain?
In Python, there exists no such list indexing syntax. Just because it makes sense to you does not mean it's valid Python code. Since no such syntax exists for standard Python lists, to Python, it looks like you are trying to use the tuple literal 0, -1 (which is equivalent to (0, -1)) as a single index to your list. Python lists do not support indexing via tuples, therefore the error.
You can do two things with that bracket notation. You can retrieve one element:
return lst[0] # the 0th element of the list
or you can retrieve a continuous slice of elements:
return lst[1:9:2] # a sublist containing every 2nd element from index 1 until 9
# so, incides 1, 3, 5, and 7.
The "solution" here is extracting the 0th element and the last element of the original list, and putting them in a new list. Technically, you could use a list slice to do this, by giving it a "step size" equal to the length of the list minus one:
return lst[::len(lst) - 1]
but that's less clear to look at than the solution you've been given.
Importantly, there are some classes in third-party libraries (e.g. numpy.array) that do let you use array[2, 3] syntax. This is not a base language feature, and it's accomplished by overriding the method that gets called when you use bracket notation to access something on the object, to make it not return an error when you put in the tuple (2, 3). In the case of np.array, it's to make it more familiar to mathematicians - array[2, 3] functions similarly to array[2][3].

for loop inclusive or exclusive in python

When I run the following code, it prints. However, I expected only one 1 rather than two.
for i in (1,1):
print(i)
Output
1
1
You are iterating over a tuple which contains two elements with value 1 so it prints 1 twice. Your code is equivalent to:
list = [1, 1]
for item in list:
print(item)
If you want to loop over a range of numbers:
for i in range(1, 2):
print(i)
Or if you want to print unique numbers or values in list or tuple convert it into the set it will automatically remove the duplicates
newList = set(list)
for value in newList:
print(value)
Sets and tuples are different. I suspect you are confusing them. On a set:
for i in {1, 1}:
print(i)
1
On a tuple:
for i in (1, 1):
print(i)
1
1
Think of sets as being like sets in math, and tuples as being more like sequences - you can have redundancies in a sequence, but not in a set.
After reading #KeshavGarg's answer, I suspect you thought that (a,b) in Python would mean stuff in a through b. As you're probably aware by now, this is not the case - you need range to get that. Interestingly (and I admit tangentially), the syntax we're discussing here varies by language. In MATLAB, the range syntax looks a lot more like what I assume you thought the Python range syntax was:
>> for i=1:4
disp(i)
end
There has been some discussion of implementing range literals (a la Matlab) in Python. This introduces a variety of interesting new problems, which you can read about in the documentation linked in the previous sentence.
For loops are always inclusive in Python: they always run over all elements of the iterator (other than exceptions such as break, etc.). What probably has you confused is the range syntax. range(1,1) will create a range object with one element. It is the range function, not the for-loop, that is exclusive, in the sense of not including the stop argument in the range object.

Recursion in Python 3.2

I am trying to wrap my head around recursion and have posted a working algorithm to produce all the subsets of a given list.
def genSubsets(L):
res = []
if len(L) == 0:
return [[]]
smaller = genSubsets(L[:-1])
extra = L[-1:]
new = []
for i in smaller:
new.append(i+extra)
return smaller + new
Let's say my list is L = [0,1], correct output is [[],[0],[1],[0,1]]
Using print statements I have narrowed down that genSubsets is called twice before I ever get to the for loop. That much I get.
But why does the first for loop initiate a value of L as just [0] and the second for loop use [0,1]? How exactly do the recursive calls work that incorporate the for loop?
I think this would actually be easier to visualize with a longer source list. If you use [0, 1, 2], you'll see that the recursive calls repeatedly cut off the last item from the list. That is, recusion builds up a stack of recursive calls like this:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0])
genSubsets([])
At this point it hits the "base case" of the recursive algorithm. For this function, the base case is when the list given as a parameter is empty. Hitting the base case means it returns an list containing an empty list [[]]. Here's how the stack looks when it returns:
genSubsets([0,1,2])
genSubsets([0,1])
genSubsets([0]) <- gets [[]] returned to it
So that return value gets back to the previous level, where it is saved in the smaller variable. The variable extra gets assigned to be a slice including only the last item of the list, which in this case is the whole contents, [0].
Now, the loop iterates over the values in smaller, and adds their concatenation with extra to new. Since there's just one value in smaller (the empty list), new ends up with just one value too, []+[0] which is [0]. I assume this is the value you're printing out at some point.
Then the last statement returns the concatenation of smaller and new, so the return value is [[],[0]]. Another view of the stack:
genSubsets([0,1,2])
genSubsets([0,1]) <- gets [[],[0]] returned to it
The return value gets assigned to smaller again, extra is [1], and the loop happens again. This time, new gets two values, [1] and [0,1]. They get concatenated onto the end of smaller again, and the return value is [[],[0],[1],[0,1]]. The last stack view:
genSubsets([0,1,2]) <- gets [[],[0],[1],[0,1]] returned to it
The same thing happens again, this time adding 2s onto the end of each of the items found so far. new ends up as [[2],[0,2],[1,2],[0,1,2]].
The final return value is [[],[0],[1],[0,1],[2],[0,2],[1,2],[0,1,2]]
I am no big fan of trying to visualize the entire call graph for recursive function to understand what they do.
I believe there is a much simpler way:
Enter fairy tale land where recursive functions do the right thing™.
Just assume that genSubsets(L) works:
# This computes the powerset of the list L minus the last element
smaller = genSubsets(L[:-1])
Because this magically worked, the only entries that are missing are those, that contain the last element.
This fragment constructs all those missing subsets:
new = []
for i in smaller:
new.append(i+extra)
Now we have those subsets containing the last element in new and we have those subsets not containing the last element in smaller.
It follows that we must now have all subsets, so we can return new + smaller.
The only thing left is the base case to make sure the recursion stops. Because the empty set (or list in this case) is an element of every power set, we can use that to stop the recursion: Requesting the powerset of an empty set is a set containing the empty set. So our base case is correct. Since every recursive step removes one element off the list, the base case must be encountered at some time.
Thus, the code really does produce the power set.
Note: The principle behind this is that of induction. If something works for some known n0, and we can prove that: The algorithm working for n implies it works for n+1, it must thus work for all n &geq; n0.

Python error: IndexError: list assignment index out of range

a=[]
a.append(3)
a.append(7)
for j in range(2,23480):
a[j]=a[j-2]+(j+2)*(j+3)/2
When I write this code, it gives an error like this:
Traceback (most recent call last):
File "C:/Python26/tcount2.py", line 6, in <module>
a[j]=a[j-2]+(j+2)*(j+3)/2
IndexError: list assignment index out of range
May I know why and how to debug it?
Change this line of code:
a[j]=a[j-2]+(j+2)*(j+3)/2
to this:
a.append(a[j-2] + (j+2)*(j+3)/2)
You're adding new elements, elements that do not exist yet. Hence you need to use append: since the items do not exist yet, you cannot reference them by index. Overview of operations on mutable sequence types.
for j in range(2, 23480):
a.append(a[j - 2] + (j + 2) * (j + 3) / 2)
The reason for the error is that you're trying, as the error message says, to access a portion of the list that is currently out of range.
For instance, assume you're creating a list of 10 people, and you try to specify who the 11th person on that list is going to be. On your paper-pad, it might be easy to just make room for another person, but runtime objects, like the list in python, isn't that forgiving.
Your list starts out empty because of this:
a = []
then you add 2 elements to it, with this code:
a.append(3)
a.append(7)
this makes the size of the list just big enough to hold 2 elements, the two you added, which has an index of 0 and 1 (python lists are 0-based).
In your code, further down, you then specify the contents of element j which starts at 2, and your code blows up immediately because you're trying to say "for a list of 2 elements, please store the following value as the 3rd element".
Again, lists like the one in Python usually aren't that forgiving.
Instead, you're going to have to do one of two things:
In some cases, you want to store into an existing element, or add a new element, depending on whether the index you specify is available or not
In other cases, you always want to add a new element
In your case, you want to do nbr. 2, which means you want to rewrite this line of code:
a[j]=a[j-2]+(j+2)*(j+3)/2
to this:
a.append(a[j-2]+(j+2)*(j+3)/2)
This will append a new element to the end of the list, which is OK, instead of trying to assign a new value to element N+1, where N is the current length of the list, which isn't OK.
At j=2 you're trying to assign to a[2], which doesn't exist yet. You probably want to use append instead.
If you want to debug it, just change your code to print out the current index as you go:
a=[]
a.append(3)
a.append(7)
for j in range(2,23480):
print j # <-- this line
a[j]=a[j-2]+(j+2)*(j+3)/2
But you'll probably find that it errors out the second you access a[2] or higher; you've only added two values, but you're trying to access the 3rd and onward.
Try replacing your list ([]) with a dictionary ({}); that way, you can assign to whatever numbers you like -- or, if you really want a list, initialize it with 23479 items ([0] * 23479).
Python lists must be pre-initialzed. You need to do a = [0]*23480
Or you can append if you are adding one at a time. I think it would probably be faster to preallocate the array.
Python does not dynamically increase the size of an array when you assign to an element. You have to use a.append(element) to add an element onto the end, or a.insert(i, element) to insert the element at the position before i.

Categories

Resources