What a solitaire negative number on range does? - python

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.

Related

How can I find the minimum in Python?

This function finds the maximum and minimum values.
n=int(input())
array=list(map(int,input().split))
for i in range(n):
max=array[0]
if i>0:
if max<array[i]:
max=array[i]
for i in range(n):
min=array[0]
if i>0:
if min>array[i]:
min=array[i]
print(max,end='')
print( min)
The maximum value comes out normally, but the minimum value comes out as the first array value. I can't find what is wrong.
The maximum value comes out normally
I think it does only because either your first or last value is the max of the sequence. Because you completely reset both min and max on each iteration. So the entire loop is useless, any iteration but the last has no effect.
The initialisation should be outside the loop, not inside of it:
max=array[0]
for i in range(n):
if i>0:
if max<array[i]:
max=array[i]
min=array[0]
for i in range(n):
if i>0:
if min>array[i]:
min=array[i]
At which point the check on the index is clearly unnecessary: either eat the unnecessary comparison of array[0] to array[0] (it's not harmful per-se), or just skip the index when iterating:
max=array[0]
for i in range(1, n):
if max<array[i]:
max=array[i]
min=array[0]
for i in range(1, n):
if min>array[i]:
min=array[i]
In essence you've written a very complicated version of:
if array[0] < array[-1]:
max = array[-1]
else:
max = array[0]
if array[0] > array[-1]:
min = array[-1]
else:
min = array[0]
Now for further improvements, under the assumption that you're trying to learn we'll ignore that min and max are already built-in functions and thus the entire thing is redundant (although you should not name your own variables the same as builtins, as that creates confusion):
n is useless, because it's not checked against array and array has its own length, n can only trigger unnecessary errors if it exceeds len(array), or skip item if it's smaller than len(array). n might be useful if e.g. the input was gathered in a loop of validated input() call, which is not the case.
Good error handling would probably check that the length of the array is at least 1, otherwise the script will blow up (although it would also need to check that individual input values are valid before converting to integers so...)
You can extract min and max in the same loop, no need to loop twice (probably not very important in this case).
But Python also has good support for iterators, so you should avoid explicit indexing when not necessary, which it is not here.
And my take, still within the realm of trying to learn, would be:
array=list(map(int,input().split))
low = high = array[0]
for candidate in array[1:]:
if candidate > high:
high = candidate
if candidate < low:
low = candidate
print(f"{low} ... {high}")
An other fun alternative is to sort the array and take the first and last elements of the now-sorted array:
array=list(map(int,input().split))
low, *_, high = sorted(array)
print(f"{low} ... {high}")
although it has the drawback that it only works on array of length 2+ where the original works on "singleton" arrays.
Use the min and max keyword from python for this.
E.g.
my_list = [0, -5, 3, 1, 10]
min_value = min(my_list)
max_value = max(my_list)
print(f'Min value: {min_value}, max value: {max_value}')
Do you just want the min and max of a list of integers a user inputs? Use the build-in functions of python for that:
list_of_user_input = []
n_input = int(input("How many variables do you want to enter: "))
for i in range(0, n_input):
number = int(input(f"Enter variable {i + 1}: "))
list_of_user_input.append(number)
print(f"The min of your list is {min(list_of_user_input)}")
print(f"The max of your list is {max(list_of_user_input)}")

How do we use minus numbers for step in range

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.

Python list first n entries in a custom base number system

I am sorry if the title is a misnomer and/or doesn't properly describe what this is all about, you are welcome to edit the title to make it clear once you understand what this is about.
The thing is very simple, but I find it hard to describe, this thing is sorta like a number system, except it is about lists of integers.
So we start with a list of integers with only zero, foreach iteration we add one to it, until a certain limit is reached, then we insert 1 at the start of the list, and set the second element to 0, then iterate over the second element until the limit is reached again, then we add 1 to the first element and set the second element 0, and when the first element reaches the limit, insert another element with value of 1 to the start of the list, and zero the two elements after it, et cetera.
And just like this, when a place reaches limit, zero the place and the places after it, increase the place before it by one, and when all available places reach limit, add 1 to the left, for example:
0
1
2
1, 0
1, 1
1, 2
2, 0
2, 1
2, 2
1, 0, 0
The limit doesn't have to be three.
This is what I currently have that does something similar to this:
array = []
for c in range(26):
for b in range(26):
for a in range(26):
array.append((c, b, a))
I don't want leading zeroes but I can remove them, but I can't figure out how to do this with a variable number of elements.
What I want is a function that takes two arguments, limit (or base) and number of tuples to be returned, and returns the first n such tuples in order.
This must be very simple, but I just can't figure it out, and Google returns completely irrelevant results, so I am asking for help here.
How can this be done? Any help will truly be appreciated!
Hmm, I was thinking about something like this, but very unfortunately I can't make it work, please help me figure out why it doesn't work and how to make it work:
array = []
numbers = [0]
for i in range(1000):
numbers[-1] += 1
while 26 in numbers:
index = numbers.index(26)
numbers[index:] = [0] * (len(numbers) - index)
if index != 0:
numbers[index - 1] += 1
else:
numbers.insert(0, 1)
array.append(numbers)
I don't quite understand it, my testing shows everything inside the loop work perfectly fine outside the loop, the results are correct, but it just simply magically will not work in a loop, I don't know the reason for this, it is very strange.
I discovered the fact that if I change the last line to print(numbers) then everything prints correctly, but if I use append only the last element will be added, how so?
from math import log
def number_to_base(n,base):
number=[]
for digit in range(int(log(n+0.500001,base)),-1,-1):
number.append(n//base**digit%base)
return number
def first_numbers_in_base(n,base):
numbers=[]
for i in range(n):
numbers.append(tuple(number_to_base(i,base)))
return numbers
#tests:
print(first_numbers_in_base(10,3))
print(number_to_base(1048,10))
print(number_to_base(int("10201122110212",3),3))
print(first_numbers_in_base(25,10))
I finally did it!
The logic is very simple, but the hard part is to figure out why it won't work in a loop, turns out I need to use .copy(), because for whatever reason, doing an in-place modification to a list directly modifies the data reside in its memory space, such behavior modifies the same memory space, and .append() method always appends the latest data in a memory space.
So here is the code:
def steps(base, num):
array = []
numbers = [0]
for i in range(num):
copy = numbers.copy()
copy[-1] += 1
while base in copy:
index = copy.index(base)
copy[index:] = [0] * (len(copy) - index)
if index != 0:
copy[index - 1] += 1
else:
copy.insert(0, 1)
array.append(copy)
numbers = copy
return array
Use it like this:
steps(26, 1000)
For the first 1000 lists in base 26.
Here is a a function, that will satisfy original requirements (returns list of tuples, first tuple represents 0) and is faster than other functions that have been posted to this thread:
def first_numbers_in_base(n,base):
if n<2:
if n:
return [(0,)]
return []
numbers=[(0,),(1,)]
base-=1
l=-1
num=[1]
for i in range(n-2):
if num[-1]==base:
num[-1]=0
for i in range(l,-1,-1):
if num[i]==base:
num[i]=0
else:
num[i]+=1
break
else:
num=[1]+num
l+=1
else:
num[-1]+=1
numbers.append(tuple(num))#replace tuple(num) with num.copy() if you want resutl to contain lists instead of tuples.
return numbers

Why doesn't my string reversal my_str[len(my_str)-1:-1:-1] produce output?

TL;DR: Why doesn't my_str[6:-1:-1] work while my_str[6::-1] does?
I have a string:
my_str="abcdefg"
I have to reverse parts of the string, which I generally do with
my_str[highest_included_index:lowest_included_index-1:-1]
For example,
# returns the reverse of all values between indices 2 and 6 (inclusive)
my_str[6:2-1:-1]
'gfedc'
This pattern breaks down if I want to include the 0 index in my reversal:
my_str[6:0-1:-1] # does NOT work
''
my_str[6:-1:-1] # also does NOT work
''
my_str[6::-1] # works as expected
'gfedcba'
Do I have to add an edge case that checks if the lowest index I want to include in a reversed string is zero and not include it in my reversal? I.e., do I have to do
for low in range(0,5):
if low == 0:
my_str[6::-1]
else:
my_str[6:low-1:-1]
That seems... unwieldy and not what I expect from python.
Edit:
The closest thing I could find to documentation of this is here:
s[i:j:k]
...
The slice of s from i to j with step k is defined as the sequence of items with index x = i + n*k such that 0 <= n < (j-i)/k. In other words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j is reached (but never including j). When k is positive, i and j are reduced to len(s) if they are greater. When k is negative, i and j are reduced to len(s) - 1 if they are greater. If i or j are omitted or None, they become “end” values (which end depends on the sign of k). Note, k cannot be zero. If k is None, it is treated like 1.
However, this mentions nothing about a negative j being maximized to zero, which it appears is the current behavior.
my_str[6:0-1:-1] # does NOT work
''
my_str[6:-1:-1] # also does NOT work
''
Yes, they do work -- as documented. A negative index is the sequence position, counting from the right. These expressions are equivalent to
my_str[6:-1:-1]
Since 6 and -1 denote the same position, the result is an empty string. However, if you give a value that is at or past the string start, such as
my_str[6:-10:-1]
then you see the expected reversal, just as if you'd specified 6::-1
Yes, you have to make a special case for the discontinuity in indexing.
#something like this?
x='my test string'
print(x[3:7][::-1])

How to explain the reverse of a sequence by slice notation a[::-1]

From the python.org tutorial
Slice indices have useful defaults; an omitted first index defaults to zero, an omitted second index defaults to the size of the string being sliced.
>>> a = "hello"
>>> print(a[::-1])
olleh
As the tutorial says a[::-1] should equals to a[0:5:-1]
but a[0:5:-1] is empty as follows:
>>> print(len(a[0:5:-1]))
0
The question is not a duplicate of explain-slice-notation. That question is about the general use of slicing in python.
I think the docs are perhaps a little misleading on this, but the optional arguments of slicing if omitted are the same as using None:
>>> a = "hello"
>>> a[::-1]
'olleh'
>>> a[None:None:-1]
'olleh'
You can see that these 2 above slices are identical from the CPython bytecode:
>>> import dis
>>> dis.dis('a[::-1]') # or dis.dis('a[None:None:-1]')
1 0 LOAD_NAME 0 (a)
3 LOAD_CONST 0 (None)
6 LOAD_CONST 0 (None)
9 LOAD_CONST 2 (-1)
12 BUILD_SLICE 3
15 BINARY_SUBSCR
16 RETURN_VALUE
For a negative step, the substituted values for None are len(a) - 1 for the start and -len(a) - 1 for the end:
>>> a[len(a)-1:-len(a)-1:-1]
'olleh'
>>> a[4:-6:-1]
'olleh'
>>> a[-1:-6:-1]
'olleh'
This may help you visualize it:
h e l l o
0 1 2 3 4 5
-6 -5 -4 -3 -2 -1
You are confused with the behavior of the stepping. To get the same result, what you can do is:
a[0:5][::-1]
'olleh'
Indeed, stepping wants to 'circle' around backwards in your case, but you are limiting it's movement by calling a[0:5:-1].
All it does is slice. You pick. start stop and step so basically you're saying it should start at the beginning until the beginning but going backwards (-1).
If you do it with -2 it will skip letters:
>>> a[::-2]
'olh'
When doing [0:5:-1] your'e starting at the first letter and going back directly to 5 and thus it will stop. only if you try [-1::-1] will it correctly be able to go to the beginning by doing steps of negative 1.
Edit to answer comments
As pointed out the documentation says
an omitted second index defaults to the size of the string being
sliced.
Lets assume we have str with len(str) = 5. When you slice the string and omit, leave out, the second number it defaults to the length of the string being sliced, in this case - 5.
i.e str[1:] == str[1:5], str[2:] == str[2:5]. The sentence refers to the length of the original object and not the newly sliced object.
Also, this answer is great
a[0:5:-1] does not make much sense, since when you use this notation the indices mean: a[start:end:step]. When you use a negative step your end value needs to be at an "earlier" position than your start value.
You'll notice that the third slice argument, the step, is not presented in the part of the tutorial you quoted. That particular snippet assumes a positive step.
When you add in the possibility of a negative step, the behavior is actually pretty intuitive. An empty start parameter refers to whichever end of the sequence one would start at to step through the whole sequence in the direction indicated by the step value. In other words it refers to the lowest index (to count up) if you have a positive step, and the highest index (to count down) if you have a negative step. Likewise, an empty end parameter refers to whichever end of the sequence one would end up at after stepping through in the appropriate direction.
The docs simply aren't correct about the default values as you've pointed out. However, they're consistent other than that minor error. You can view the docs I am referring to here: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
Note that the behavior is definitionaly correct according to the docs:
The slice of s from i to j with step k is defined as the sequence of
items with index x = i + n*k such that 0 <= n < (j-i)/k. In other
words, the indices are i, i+k, i+2*k, i+3*k and so on, stopping when j
is reached (but never including j).
When you do:
>>> a = "hello"
>>> y = a[0:5:-1]
we have that i == 0, j == 5, and k == -1. So we are grabbing items at index x = i + n*k for n starting at 0 and going up to (j-i)/k. However, observe that (j-i)/k == (5-0)/-1 == -5. There are no n such that 0 <= n < -5, so you get the empty string:
>>> y
''
Do a[start:stop][::step] when in doubt (it's almost always what we want)
It's almost always the case that when you pass a negative step to something like x[start:stop:step], what you want to happen is for the sub selection to happen first, and then just go backwards by step (i.e. we usually want x[start:stop][::step].
Futhermore, to add to the confusion, it happens to be the case that
x[start:stop:step] == x[start:stop][::step]
if step > 0. For example:
>>> x = list(range(10))
>>> x
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x[2:6:2]
[2, 4]
>>> x[2:6][::2]
[2, 4]
>>> x[1:10][::3]
[1, 4, 7]
>>> x[1:10:3]
[1, 4, 7]
Unfortunately, this doesn't hold when step < 0, even though it's tempting to think that it should.
After being burned by this a couple times, I realized it's just safer to always do the step clause after you perform the start:stop slice. So I almost always start with y = x[start:stop][::step], at least when prototyping or creating a new module where correctness/readability is the primiary concern. This is less performant than doing a single slice, but if performance is an issue, then you can do the less readable:
y = x[start:stop:step] if step > 0 else x[stop:start:step]
HTH.
For Python slicing for a sequence[start:stop:step], have derived these rules:
start:stop = start:stop:1
start:stop:(+ or -) step - It means when traversing skip N items in the sequence. However, (-) indicates backward traversal
Remember, position of last item in sequence is -1, and the one before than is -2, and so on..
# start:stop: +step Rules
Always traverse in forward
Always start from beginning of sequence as its a positive step ( forward )
Start at requested position, stop at requested position but exclude the item stop position
Default start: If start is not provided, start at 0
Default stop: if stop is not provided, it means until the end of the sequence including the last value
If item at stop position is not reachable (item is beyond the end of sequence traversal), slice does not return anything
# start:stop:-step Rules
Always traverse in reverse
If start position is provided, start from there, but traverse in reverse ( its a step back )
If stop is provided, stop traversing there but exclude this
Default start: If start position is not provided, start position is the last position of the sequence ( since negative traversal)
Default stop: If stop is not provided, it is the beginning of the list ( position 0)
If item at stop position is not reachable (item is beyond the end of sequence traversal), slice does not return anything

Categories

Resources