Understanding interval function and its parameters in python Hetland's book example - python

Got this example function from Hetland's python book.
def interval(start, stop=None, step=1):
if stop is None:
start, stop=0, start
result = []
i = start
while i < stop:
result.append(i)
i += step
return result
What are both starts doing in the if statement? And why is it there twice? I changed the latter start to an int and that changed the length of my list to that int.
I also can't seem to wrap my head around the interation part. In the example, start = 10. So, when iterating... while 10 < 0 it will continue to grow the list, increasing the count by step=1 each time. But, 10 isn't less than 0. How'd run in the first place?

The assignment should be parsed as
(start, stop) = (0, start)
That is, interval(10) is equivalent to interval(0, 10). Even though the first parameter's name is start, it's really the stop value if only one argument is provided.
The while loop is equivalent to the more straight-forward for loop
for i in range(start, stop + 1, step):
result.append(i)
except interval lets start, stop, and step all have types other than int.
A better design might be to require the use of keyword-only arguments, so that there's no ambiguity in the body of the function in how each parameter is used:
def interval(*, start=0, stop, step=1):
result = []
while start < stop:
result.append(start)
start += step
return result
Now you can call interval(stop=10) or interval(start=3, stop=10); either way, the parameter start is actually the starting value in both cases. It's a trade-off, though, in that you can no longer write interval(10) for what would be expected as the most common use case.

Related

Why the final index using range in python is not equal to end parameter? [duplicate]

This question already has answers here:
Why are slice and range upper-bound exclusive?
(6 answers)
Closed last month.
>>> range(1,11)
gives you
[1,2,3,4,5,6,7,8,9,10]
Why not 1-11?
Did they just decide to do it like that at random or does it have some value I am not seeing?
Because it's more common to call range(0, 10) which returns [0,1,2,3,4,5,6,7,8,9] which contains 10 elements which equals len(range(0, 10)). Remember that programmers prefer 0-based indexing.
Also, consider the following common code snippet:
for i in range(len(li)):
pass
Could you see that if range() went up to exactly len(li) that this would be problematic? The programmer would need to explicitly subtract 1. This also follows the common trend of programmers preferring for(int i = 0; i < 10; i++) over for(int i = 0; i <= 9; i++).
If you are calling range with a start of 1 frequently, you might want to define your own function:
>>> def range1(start, end):
... return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Although there are some useful algorithmic explanations here, I think it may help to add some simple 'real life' reasoning as to why it works this way, which I have found useful when introducing the subject to young newcomers:
With something like 'range(1,10)' confusion can arise from thinking that pair of parameters represents the "start and end".
It is actually start and "stop".
Now, if it were the "end" value then, yes, you might expect that number would be included as the final entry in the sequence. But it is not the "end".
Others mistakenly call that parameter "count" because if you only ever use 'range(n)' then it does, of course, iterate 'n' times. This logic breaks down when you add the start parameter.
So the key point is to remember its name: "stop".
That means it is the point at which, when reached, iteration will stop immediately. Not after that point.
So, while "start" does indeed represent the first value to be included, on reaching the "stop" value it 'breaks' rather than continuing to process 'that one as well' before stopping.
One analogy that I have used in explaining this to kids is that, ironically, it is better behaved than kids! It doesn't stop after it supposed to - it stops immediately without finishing what it was doing. (They get this ;) )
Another analogy - when you drive a car you don't pass a stop/yield/'give way' sign and end up with it sitting somewhere next to, or behind, your car. Technically you still haven't reached it when you do stop. It is not included in the 'things you passed on your journey'.
I hope some of that helps in explaining to Pythonitos/Pythonitas!
Exclusive ranges do have some benefits:
For one thing each item in range(0,n) is a valid index for lists of length n.
Also range(0,n) has a length of n, not n+1 which an inclusive range would.
It works well in combination with zero-based indexing and len(). For example, if you have 10 items in a list x, they are numbered 0-9. range(len(x)) gives you 0-9.
Of course, people will tell you it's more Pythonic to do for item in x or for index, item in enumerate(x) rather than for i in range(len(x)).
Slicing works that way too: foo[1:4] is items 1-3 of foo (keeping in mind that item 1 is actually the second item due to the zero-based indexing). For consistency, they should both work the same way.
I think of it as: "the first number you want, followed by the first number you don't want." If you want 1-10, the first number you don't want is 11, so it's range(1, 11).
If it becomes cumbersome in a particular application, it's easy enough to write a little helper function that adds 1 to the ending index and calls range().
It's also useful for splitting ranges; range(a,b) can be split into range(a, x) and range(x, b), whereas with inclusive range you would write either x-1 or x+1. While you rarely need to split ranges, you do tend to split lists quite often, which is one of the reasons slicing a list l[a:b] includes the a-th element but not the b-th. Then range having the same property makes it nicely consistent.
The length of the range is the top value minus the bottom value.
It's very similar to something like:
for (var i = 1; i < 11; i++) {
//i goes from 1 to 10 in here
}
in a C-style language.
Also like Ruby's range:
1...11 #this is a range from 1 to 10
However, Ruby recognises that many times you'll want to include the terminal value and offers the alternative syntax:
1..10 #this is also a range from 1 to 10
Consider the code
for i in range(10):
print "You'll see this 10 times", i
The idea is that you get a list of length y-x, which you can (as you see above) iterate over.
Read up on the python docs for range - they consider for-loop iteration the primary usecase.
Basically in python range(n) iterates n times, which is of exclusive nature that is why it does not give last value when it is being printed, we can create a function which gives
inclusive value it means it will also print last value mentioned in range.
def main():
for i in inclusive_range(25):
print(i, sep=" ")
def inclusive_range(*args):
numargs = len(args)
if numargs == 0:
raise TypeError("you need to write at least a value")
elif numargs == 1:
stop = args[0]
start = 0
step = 1
elif numargs == 2:
(start, stop) = args
step = 1
elif numargs == 3:
(start, stop, step) = args
else:
raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
i = start
while i <= stop:
yield i
i += step
if __name__ == "__main__":
main()
The range(n) in python returns from 0 to n-1. Respectively, the range(1,n) from 1 to n-1.
So, if you want to omit the first value and get also the last value (n) you can do it very simply using the following code.
for i in range(1, n + 1):
print(i) #prints from 1 to n
It's just more convenient to reason about in many cases.
Basically, we could think of a range as an interval between start and end. If start <= end, the length of the interval between them is end - start. If len was actually defined as the length, you'd have:
len(range(start, end)) == start - end
However, we count the integers included in the range instead of measuring the length of the interval. To keep the above property true, we should include one of the endpoints and exclude the other.
Adding the step parameter is like introducing a unit of length. In that case, you'd expect
len(range(start, end, step)) == (start - end) / step
for length. To get the count, you just use integer division.
Two major uses of ranges in python. All things tend to fall in one or the other
integer. Use built-in: range(start, stop, step). To have stop included would mean that the end step would be assymetric for the general case. Consider range(0,5,3). If default behaviour would output 5 at the end, it would be broken.
floating pont. This is for numerical uses (where sometimes it happens to be integers too). Then use numpy.linspace.

Is there a simple one line way to exclude the first element of a range function in Python?

Just for clarification, the step can also be negative. I'm currently just doing this.
def excl_range(start, end, step):
if step > 0:
return range(start + 1, end, step)
return range(start - 1, end, step)
I'm Just wondering if there is a cleaner way preferably without the use of a new function to do this.
A rarely mentioned or used fact about range is that it is a complete sequence type in python 3. That means that you can index objects directly, including with slices:
range(...)[1:]
This is equivalent to incrementing start by step. Notice that the sign of step is irrelevant: the second element is always start + step by definition:
range(start + step, end, step)
You can also exclude the element during iteration:
it = iter(range(...))
next(it)
for x in it:
...
This works for any iterable, not just range. You can do the equivalent with itertools.islice:
for x in islice(range(...), 1):
...
You could consider doing it like this:-
step = 1
start = 10
end = 100
for i in range(start + 1 if step > 0 else start - 1, end, step):
pass # your code goes here

Quicksort in place Python

I am extremely new to Python and i am trying my hand in Algorithms. I was just trying my hands in the in place sorting of an array using quicksort algo.
Below is my code . When i run this there is an infinite loop as output. Can anyone kindly go through the code and let me know where i am going wrong logically :
def quicksort(arr,start,end):
if start < end:
pivot = arr[int(start + (end - start)/2)]
while end > start:
while arr[start] < pivot:
start = start + 1
while arr[end] > pivot:
end = end - 1
if start <= end:
arr[start],arr[end] = arr[end],arr[start]
start = start + 1
end = end - 1
quicksort(arr,0,end)
quicksort(arr,start,len(arr) - 1)
else:
return
arr = list(map(int,input().split(" ")))
quicksort(arr,0,len(arr) - 1)
print ("The final sorted array:",arr)
Thanks for any help in advance.
Your recursion is wrong. After doing Hoare Partition, you should do :
call quicksort from start (of data that being sorted) to index that run from the end, and
call quicksort from end (of data that being sorted) to index that run from the start
So you have to create new variables beside the argument to maintain start,end and indices that move toward each other in the partition.
In order to not change a lot of your code, i suggest you to change the argument
def quicksort(arr,left,right):
start = left
end = right
and do this in the recursion
quicksort(arr,left,end)
quicksort(arr,start,right)

recursion, finding max, why is it not stopping?

I tried to find the maximum value in a sorted list. but the recursion is not stopping. please, can somebody help me?
A = [5,16,28,43,0,1]
start = 0
end = len(A) - 1
mid = 0
print mid
def search(start, end, mid):
mid = int((start + end) / 2)
print mid
if A[mid] > [mid - 1] and A[mid] > A[mid + 1]:
return A[mid]
else:
if A[mid - 1] > A[mid + 1]:
search(start, mid, mid)
else:
search(mid, end, mid)
print search(start, end, mid)
You need to add a "basis case" (where the recursion stops).
A natural basis case for this problem: if start is equal to end, just return A[start]
EDIT:
I just looked at this and the more I look the more confused I get. Why are you using recursion to find a max? It would make more sense to use recursion to do a "binary search" to find a value inside a sorted list.
If you want to really find a max value, that's pretty easy. With recursion, we first want a "basis case" that gives us a trivial solution; then we want more code that will take us one step closer to that solution.
In this case, the basis case: we have only one value in the list; return it as the max. To be specific, if the start and end together specify just one value, return that one value. To make it proof against errors, might as well make this also handle the case where start is equal to or even greater than end.
Next remember the first value.
Next make a recursive call, but add one to start to reduce the size of the list we are considering. This is the part that takes us one step closer to a solution. Repeat this step enough times and we arrive at the basis case where there is only one value in the list to consider.
Finally compare the remembered first value with the result of the recursive call and return the larger of the two.
I'll lay it out in psuedocode for you:
BASIS CASE: start and end specify one value: return A[start]
save A[0] in a variable
save recursive_call_to_this_function(start+1, end) in a variable
compare two saved values and return the larger
Once you have tried to write the above in code, peek below this line for my working tested solution.
def recursive_max(start, end):
if start >= end - 1:
return A[start]
x0 = A[start]
x1 = recursive_max(start+1, end)
if x0 >= x1:
return x0
else:
return x1
print recursive_max(start, end)
I agree with most of steveha's answer, but rather than picking off one element at a time I'd suggest dividing the list into halves and finding the max in each half. You won't do any fewer comparisons, but the growth of the recursive stack will be O(log(len(A))) rather than O(len(A)). For large lists this would be the difference between getting a stack overflow or not.
My implementation (which takes the list as an argument rather than expecting it to be global) follows:
def recursive_max(value_list, start, end):
if start >= end:
return value_list[start]
mid = start + (end - start) // 2
lower_half_max = recursive_max(value_list, start, mid)
upper_half_max = recursive_max(value_list, mid+1, end)
if lower_half_max > upper_half_max:
return lower_half_max
else:
return upper_half_max

Why does range(start, end) not include end? [duplicate]

This question already has answers here:
Why are slice and range upper-bound exclusive?
(6 answers)
Closed last month.
>>> range(1,11)
gives you
[1,2,3,4,5,6,7,8,9,10]
Why not 1-11?
Did they just decide to do it like that at random or does it have some value I am not seeing?
Because it's more common to call range(0, 10) which returns [0,1,2,3,4,5,6,7,8,9] which contains 10 elements which equals len(range(0, 10)). Remember that programmers prefer 0-based indexing.
Also, consider the following common code snippet:
for i in range(len(li)):
pass
Could you see that if range() went up to exactly len(li) that this would be problematic? The programmer would need to explicitly subtract 1. This also follows the common trend of programmers preferring for(int i = 0; i < 10; i++) over for(int i = 0; i <= 9; i++).
If you are calling range with a start of 1 frequently, you might want to define your own function:
>>> def range1(start, end):
... return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Although there are some useful algorithmic explanations here, I think it may help to add some simple 'real life' reasoning as to why it works this way, which I have found useful when introducing the subject to young newcomers:
With something like 'range(1,10)' confusion can arise from thinking that pair of parameters represents the "start and end".
It is actually start and "stop".
Now, if it were the "end" value then, yes, you might expect that number would be included as the final entry in the sequence. But it is not the "end".
Others mistakenly call that parameter "count" because if you only ever use 'range(n)' then it does, of course, iterate 'n' times. This logic breaks down when you add the start parameter.
So the key point is to remember its name: "stop".
That means it is the point at which, when reached, iteration will stop immediately. Not after that point.
So, while "start" does indeed represent the first value to be included, on reaching the "stop" value it 'breaks' rather than continuing to process 'that one as well' before stopping.
One analogy that I have used in explaining this to kids is that, ironically, it is better behaved than kids! It doesn't stop after it supposed to - it stops immediately without finishing what it was doing. (They get this ;) )
Another analogy - when you drive a car you don't pass a stop/yield/'give way' sign and end up with it sitting somewhere next to, or behind, your car. Technically you still haven't reached it when you do stop. It is not included in the 'things you passed on your journey'.
I hope some of that helps in explaining to Pythonitos/Pythonitas!
Exclusive ranges do have some benefits:
For one thing each item in range(0,n) is a valid index for lists of length n.
Also range(0,n) has a length of n, not n+1 which an inclusive range would.
It works well in combination with zero-based indexing and len(). For example, if you have 10 items in a list x, they are numbered 0-9. range(len(x)) gives you 0-9.
Of course, people will tell you it's more Pythonic to do for item in x or for index, item in enumerate(x) rather than for i in range(len(x)).
Slicing works that way too: foo[1:4] is items 1-3 of foo (keeping in mind that item 1 is actually the second item due to the zero-based indexing). For consistency, they should both work the same way.
I think of it as: "the first number you want, followed by the first number you don't want." If you want 1-10, the first number you don't want is 11, so it's range(1, 11).
If it becomes cumbersome in a particular application, it's easy enough to write a little helper function that adds 1 to the ending index and calls range().
It's also useful for splitting ranges; range(a,b) can be split into range(a, x) and range(x, b), whereas with inclusive range you would write either x-1 or x+1. While you rarely need to split ranges, you do tend to split lists quite often, which is one of the reasons slicing a list l[a:b] includes the a-th element but not the b-th. Then range having the same property makes it nicely consistent.
The length of the range is the top value minus the bottom value.
It's very similar to something like:
for (var i = 1; i < 11; i++) {
//i goes from 1 to 10 in here
}
in a C-style language.
Also like Ruby's range:
1...11 #this is a range from 1 to 10
However, Ruby recognises that many times you'll want to include the terminal value and offers the alternative syntax:
1..10 #this is also a range from 1 to 10
Consider the code
for i in range(10):
print "You'll see this 10 times", i
The idea is that you get a list of length y-x, which you can (as you see above) iterate over.
Read up on the python docs for range - they consider for-loop iteration the primary usecase.
Basically in python range(n) iterates n times, which is of exclusive nature that is why it does not give last value when it is being printed, we can create a function which gives
inclusive value it means it will also print last value mentioned in range.
def main():
for i in inclusive_range(25):
print(i, sep=" ")
def inclusive_range(*args):
numargs = len(args)
if numargs == 0:
raise TypeError("you need to write at least a value")
elif numargs == 1:
stop = args[0]
start = 0
step = 1
elif numargs == 2:
(start, stop) = args
step = 1
elif numargs == 3:
(start, stop, step) = args
else:
raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
i = start
while i <= stop:
yield i
i += step
if __name__ == "__main__":
main()
The range(n) in python returns from 0 to n-1. Respectively, the range(1,n) from 1 to n-1.
So, if you want to omit the first value and get also the last value (n) you can do it very simply using the following code.
for i in range(1, n + 1):
print(i) #prints from 1 to n
It's just more convenient to reason about in many cases.
Basically, we could think of a range as an interval between start and end. If start <= end, the length of the interval between them is end - start. If len was actually defined as the length, you'd have:
len(range(start, end)) == start - end
However, we count the integers included in the range instead of measuring the length of the interval. To keep the above property true, we should include one of the endpoints and exclude the other.
Adding the step parameter is like introducing a unit of length. In that case, you'd expect
len(range(start, end, step)) == (start - end) / step
for length. To get the count, you just use integer division.
Two major uses of ranges in python. All things tend to fall in one or the other
integer. Use built-in: range(start, stop, step). To have stop included would mean that the end step would be assymetric for the general case. Consider range(0,5,3). If default behaviour would output 5 at the end, it would be broken.
floating pont. This is for numerical uses (where sometimes it happens to be integers too). Then use numpy.linspace.

Categories