How do we use minus numbers for step in range - python

I tried to use -1 for the step in range to reverse the list, in the first code it gave an empty list and in the second, I got what I wanted.
print(list(range(0, 5, -1)))
# Output: []
print(list(range(5, -1, -1)))
# Output: [5, 4, 3, 2, 1, 0]
How do we understand this?

The range generator does a sanity check. If start is lower than end and step is negative then that's impossible - hence the empty list. In your case, you can never get to 5 by decrementing from zero.
In the second case, the range generator will stop generating when end has been reached - i.e. it does not generate the value of end

range(0, 5, -1) -> starting at 0, you cannot reach 5 by successively adding -1. No numbers are encountered along the way.
range(5, -1, -1) -> starting at 5, you can reach -1 by successively adding -1, and it yields the numbers that it'll encounter along the way.

range function is working like this if you add 3 arguments :
range(begin, end, step)
In this case you will begin from begin value, and by implementing the step, reach the (end - abs(step)) value at the end. Remember in python as the end is not reached (which differs from other languages such as Matlab for example).
You must take care of your implementation step according to the value you add as begin and end. It must be consistent, as #Kache was saying.

Related

Non-tail recursion within a for loop

Given an array of numbers, find the length of the longest increasing subsequence in the array. The subsequence does not necessarily have to be contiguous.
For example, given the array [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15], the longest increasing subsequence has length 6: it is 0, 2, 6, 9, 11, 15.
One of the solutions to the above problem uses non-tail recursion within a for loop, and I am having trouble making sense of it. I don't understand when the code after the recursive call in the for loop is executed, and I can't visualize the entire execution process of the whole solution.
def longest_increasing_subsequence(arr):
if not arr:
return 0
if len(arr) == 1:
return 1
max_ending_here = 0
for i in range(len(arr)):
ending_at_i = longest_increasing_subsequence(arr[:i])
if arr[-1] > arr[i - 1] and ending_at_i + 1 > max_ending_here:
max_ending_here = ending_at_i + 1
return max_ending_here
The description of the solution is as follows:
Assume that we already have a function that gives us the length of the longest increasing subsequence. Then we’ll try to feed some part of our input array back to it and try to extend the result. Our base cases are: the empty list, returning 0, and an array with one element, returning 1.
Then,
For every index i up until the second to last element, calculate longest_increasing_subsequence up to there.
We can only extend the result with the last element if our last element is greater than arr[i] (since otherwise, it’s not increasing).
Keep track of the largest result.
Source: https://www.dailycodingproblem.com/blog/longest-increasing-subsequence/
**EDITS**:
What I mean by I don't understand when the code after the recursive call in the for loop is executed. Here is my understanding:
Some code calls lis([0, 8, 4, 12, 2]).
arr = [0, 8, 4, 12, 2] doesn't meet either of the two base cases.
The for loop makes the first call when i = 0 in the line ending_at_i = lis([]). This is the first base case, so it returns 0. I can't understand why control doesn't return to the for loop so that ending_at_i is set to 0, and the if condition is executed (because it surely isn't checked else [][-1] would throw an error), after which we can move on to the for loop making the second call when i = 1, third call when i = 2 which would branch into two calls, and so on.
Here's how this function works. Fist, it handles the degenerate cases where the list length is 0 or 1.
It then looks for the solution when the list length is >= 2. There are two possibilities for the longest sequence: (1) It may contain the last number in the list, or (2) It may not contain the last number in the list.
For case (1), if the last number in the list is in the longest sequence, then the number before it in the longest sequence must be one of the earlier numbers. Suppose the number before it in the sequence is at position x. Then the longest sequence is the longest sequence taken from the numbers in the list up to and including x, plus the last number in the list. So it recurses on all of the possible positions of x, which are 0 through the list length minus 2. It iterates i over range(len(arr)), which is 0 through len(arr)-1). But it then uses i as the upper bound in the slice, so the last element in the slice corresponds to indices -1 through len(arr)-2. In the case of -1, this is an empty slice, which handles the case where all values in the list before the last are >= the last element.
This handles case (1). For case (2), we just need to find the largest sequence from the sublist that excludes the last element. However, this check is missing from the posted code, which is why the wrong answer is given for a list like [1, 2, 3, 0]:
>>> longest_increasing_subsequence([1, 2, 3, 0])
0
>>>
Obviously the correct answer in this case is 3, not 0. This is fairly easy to fix, but somehow was left out of the posted version.
Also, as others have pointed out, creating a new slice each time it recurses is unnecessary and inefficient. All that's needed is to pass the length of the sublist to achieve the same result.
Here is a (hopefully good enough) explanation:
ending_at_i = the length of the LIS when you clip arr at the i-th index (that is, considering elements arr[0], arr[1], ..., arr[i-1].
if arr[-1] > arr[i - 1] and ending_at_i + 1 > max_ending_here
if arr[-1] > arr[i - 1] = if the last element of arr is greater than the last element of the part of arr correponding to ending_at_i
if ending_at_i + 1 > max_ending_here = if appending the last element of arr to the LIS found during computing ending_at_i is larger than the current best LIS
The recursive step is then:
Let an oracle tell you the length of the LIS in arr[:i] (= arr[0], arr[1], ..., arr[i-1])
realize that, if the last element of arr, that is, arr[-1], is larger than the last element of arr[:i], then whatever the LIS inside arr[:i] was, if you take it and append arr[-1], it will still be an LIS, except that it will be one element larger
Check whether arr[-1] is actually larger than arr[i-1], (= arr[:i][-1])
Check whether appending arr[-1] to the LIS of arr[:i] creates the new optimal solution
Repeat 1., 2., 3. for i in range(len(arr)).
The result will be the knowledge of the length of the LIS inside arr.
All that being said, since the recursive substep of this algorithm runs in O(n), there are very few worse feasible solutions to the problem.
You tagged dynamic programming, however, this is precisely the anti-example of such. Dynamic programming lets you reuse the solutions to subproblems, which is precisely what this algorithm doesn't do, hence wasting time. Check out a DP solution instead.

starting range() as len(<some_list>) VS starting range() as len(<some_list>)-1 [Inclusive/Exclusive end]

I can't seem to understand when does the end condition is included and when it's not depending of the start as a length of list or length of list deducted by one (last position) , without modifying the end with the step value
I wanted to pop elements from a list within a loop
(Note: I know that setting range(0,len(colors_list)) like this will do the trick too)
colors_list = ["green","blue","yellow","pink","violet","black"]
I tried this snippet of code, and the end was included :-
for color in range(len(colors_list),0,-1):
colors_list.pop()
print(colors_list)
Output : []
and I tried this too, but here the end was excluded :-
for color in range(len(colors_list)-1,0,-1):
colors_list.pop()
print(colors_list)
Output : ["green"]
I understand the second trial as the 0th index is not popped, but the first trial is what I dont understand, like isn't it supposed to be the same as the second ? considering that it stop befrore the 0th index ? but the first element is popped instead.
The end was excluded in both cases. You ran the loop one fewer times in the second case, because you added the -1 to the start condition. The end value is always exclusive, the start value is always inclusive.
Just listify and print the ranges and you'll see:
>>> colors_list = ["green","blue","yellow","pink","violet","black"]
>>> print(list(range(len(colors_list),0,-1)))
[6, 5, 4, 3, 2, 1]
>>> print(list(range(len(colors_list)-1,0,-1)))
[5, 4, 3, 2, 1]
No 0 on either, you just began the loop from 6 on one, and 5 on the other.

What a solitaire negative number on range does?

I was doing an exercise from John Zelle's book on Python, he asked to do the Fibonacci sequence using a loop function.
After I didn't manage to get it done, I gave a look at his resolution, which was this:
n = int(input("Enter the value of n: "))
curr, prev = 1, 1
for i in range(n-2):
curr, prev = curr+prev, curr
print("The nth Fibonacci number is", curr)
While I did understand part of what he did, the part that I missed was the (n-2) range.
I gave a look here on Stackoverflow to see about this and people say that a negative number on the range goes back to the end of the list. But in this case, if the user prompts 1, the result will be range(-1).
My guess was that the author did that so that the for loop didn't sum the first two values of the Fibonacci sequence, since they are both 1, and only after the user prompts 2 and forth, the loop actually starts adding up. Am I right on my guess?
If you enter 0 or 1 for this, the code does not enter the loop, and the result is the initial value of curr, that being 1. For any higher value, the loop will iteratively compute the proper value.
Your memory of negative values is a little bit off: a negative index will work from the opposite end of an iterable (e.g. list, tuple, string). A range is not quite in that class; the result in this case is an empty range.
CLARIFICATION after OP comment
I see your confusion. range returns an iterable of the given values. However, it looks like you've confused the limits with the index. Let's work with a general form:
r = range(left, right, step)
r[pos]
left* defaults to 0; **step defaults to 1
Here are some examples:
>>> r = range(0, 20, 2)
>>> r[-1]
18
>>> r = range(0, -1)
>>> r
[]
>>> r = range(0, -10, -2)
>>> r
[0, -2, -4, -6, -8]
>>> r[-2]
-6
Note the second and third examples, where we use negative values for endpoints. There's a distinction between a negative endpoint and a negative index. The endpoint is used to build the list; if the endpoints aren't in the order implied by the step, then the resulting range is the empty list. range(0, -1) is such an example.
Once the list is built, such as with range(0, 20, 2), then a reference into that list with a negative index will count from the right end of the list. Note the third example, making a list that goes "backward", 0 down to -8. A negative index in this case also works from the right. The negative right-end value, the negative step, and the negative index are three distinct usages.
Does that clear up things?
>>> range(-1)
range(0, -1)
So the for loop is not entered if n is 1 or 2 and curr (which is set to 1) is the result.

Generators in Python Query

I'm trying to understand Generators and have come across the following code:
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
for s in reverse([1,2,3]):
print s
I understand that we start at index 2, decrement by 1 and end at -1. What I don't get is the fact that the stopping point -1 ,I thought, should refer to "3", but it appears to refer to "1" here? :
3
2
1
Thanks
Please see https://docs.python.org/2/library/functions.html#range to see how range works. I can see it may be initially confusing to read the documentation but hope my explanation below, for your case, helps.
Specifically these lines from the above doc answers your question:
' The full form returns a list of plain integers [start, start + step, start + 2 * step, ...]. If step is positive, the last element is the largest start + i * step less than stop; if step is negative, the last element is the smallest start + i * step greater than stop'
In your case start=2(len(data)-1), stop =-l and step=-1. So the potential list of integers would be [2, 2-1, 2-2*1, 2-3*1 ...] which is [2,1,0,-1 ...]. However since your step is negative, i.e, -1, the last element would be smallest (start + i*step) greater than stop. In the potential list, the smallest item greater than stop, i.e greater than -1 is 0. So range(len(data)-1, -1, -1) returns [2,1,0]

How to understand python slicing with a negative k index?

Can someone explain why a[:5:-1] != a[:5][::-1]?
>>> a = range(10)
>>> a[:5][::-1]
[4, 3, 2, 1, 0]
>>> a[:5:-1]
[9, 8, 7, 6]
The general syntax of slicings is
a[start:stop:step]
You can omit any of the three values start, stop, or step. If you omit step, it always defaults to 1. The default values of start and stop, by contrast, depend on the sign of step: if step is positive, start defaults to 0 and stop to len(a). If step is negative, start defaults to len(a) - 1 and stop to "beginning of the list".
So a[:5:-1] is the same as a[9:5:-1] here,
while a[:5][::-1] is the same as a[0:5][4::-1].
(Note that it's impossible to give the default value for stop explicitly if step is negative. The stop value is non-inclusive, so 0 would be different from "beginning of the list". Using None would be equivalent to giving no value at all.)
What a[:5][::-1] says is that program should firstly take elements until 5th element of the dataset and then reverse them (take every one element starting with the last one).
Contrary to that, a[:5:-1] says that you should take elements until 5th element starting with the last element (take every one element starting with the last one).
a[:5] returns an array, indexes 0 through 4, that you're then negatively indexing as a second operation. a[:5:-1] indexes the original array negatively.

Categories

Resources