Python-why does it print out "dessert" - python

I'm new to programming, and obviously I can easily run a program to get the answer, but I want to get a clearer and better understanding of why this code runs "dessert". I understand len(s) is the length of the number, but what about the three numbers "-1, 0 -1"? Can I get a detailed explanation of why this prints dessert?
s = "stressed"
for i in range(len(s)-1, 0, -1):
print(s[i], end = " ")

s t r e s s e d
0 1 2 3 4 5 6 7
range function : range(start, stop, step)
len(s) = 8
len(s-1)= 7
the loop starts at 7 stops at 0 and counts(steps) bacwards by 1 (-1)
so , the loop prints
7 6 5 4 3 2 1
d e s s e r t

The code creates a range that counts down. Starting at len(s) - 1 (so 7, stressed is 8 characters long), it'll count down to 1 (the end point is not included). In other words, you are giving range() three arguments, len(s) - 1 is one argument, 0 is the second and -1 is the third, and they represent the start, stop and step arguments. See the range() type documentation.
The code then uses those values to index s; s[7] is the last character in the string, d, then s[6] is the one-but last character e, etc.
Breaking this down to the components in an interactive session:
>>> s = "stressed"
>>> len(s)
8
>>> len(s) - 1
7
>>> list(range(7, 0, -1))
[7, 6, 5, 4, 3, 2, 1]
>>> s[7]
'd'
>>> s[6]
'e'
>>> s[1]
't'
If you wanted the code to print out desserts (with the s at the end) then you need to adjust the range() to loop up to -1:
for i in range(len(s) - 1, -1, -1):

It's all about range().
It's all discussed here.
It can be used in three ways;
range(stop)
range(start, stop)
range(start, stop, step)
Where in the first case, it will give [1, 2, 3, ... stop-1], in the second case [start, start+1, start+2, ... stop-1], and in the third case [start, start+step, start+2*step, ...stop-step].
The thing to note here is that the range you get is [start, stop). Where standard interval notation has been used.
The other thing to note here is that step can be negative, which is what you have in your example.

range(len(s)-1, 0, -1)
reange(start_index, to_end_index, is_increment or is_decrement)
You call a loop with its start index, then tell it to traverse to end index and lastly increase the index or decrease the index
range(len(s)-1 *calculate the lengthof array (string), 0 *end point of traversing, -1 decrement the index)
If you use
range(0, len(s),1)
The loop will start from 0 index and traverse till last index with 1 index increment

Related

Why isn't `print(x[0:10:-1])` the same as `print(x[::-1])`?

If you define a string in Python like:
x = "1234567890"
print(x[::-1]) prints the string in reverse order, but
print(x[0:10:-1]) prints nothing.
Why don't they print the same thing?
See note 5 of the relevant 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)
Accordingly, when you specify i = 0, j = 10, k = -1 in [0:10:-1], there are no values of n which satisfy the equation 0 <= n < (j-i)/k, and therefore the range is empty.
On the other hand, in [::-1], i == j == None. Refer to the next part of note 5:
If i or j are omitted or None, they become “end” values (which end depends on the sign of k).
Since k is negative, i becomes len(s) - 1, which evaluates to 9. j cannot be represented by any number (since 0 would cut off the last character).
You should know about the basic rule x[start:stop:step]. In your code, you put two different rules: print(x[0:10:-1]) and print(x[::-1]).
When you put nothing in the start and stop index, your code will be executed using the default depends on the step. If the step is positive, then the start index will be from 0 and the stop is the length of the string. Here, you may customize the start and stop value as long as they fulfilling start < stop.
On the other hand, if the step is negative, then the start index will be from the last index of the string while the stop is 0. Here, you may also customize the start and stop value as long as they fulfilling start > stop.
In your case, you wrote print(x[0:10:-1]) that gives you nothing back. This clearly because you set 0 as the start and 10 as the stop index while the step is negative.
Reference: https://www.digitalocean.com/community/tutorials/how-to-index-and-slice-strings-in-python-3
You are looking for print(x[10:0:-1]).
>>> x = list(range(10))
>>> x[10:0:-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1]
We can model x[start:end:step] with the following pseudocode:
i = start
if start < end:
cond = lambda: i < end
else:
cond = lambda: i > start
while cond():
SELECT x[i]
i += step

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

Pythonic way to iterate through a range starting at 1

Currently if I want to iterate 1 through n I would likely use the following method:
for _ in range(1, n+1):
print(_)
Is there a cleaner way to accomplish this without having to reference n + 1 ?
It seems odd that if I want to iterate a range ordinally starting at 1, which is not uncommon, that I have to specify the increase by one twice:
With the 1 at the start of the range.
With the + 1 at the end of the range.
From the documentation:
range([start], stop[, step])
The start defaults to 0, the step can be whatever you want, except 0 and stop is your upper bound, it is not the number of iterations. So declare n to be whatever your upper bound is correctly and you will not have to add 1 to it.
e.g.
>>> for i in range(1, 7, 1): print(i)
...
1
2
3
4
5
6
>>> for i in range(1, 7, 2): print(i)
...
1
3
5
A nice feature, is that it works in reverse as well.
>>> for i in range(7, 0, -1): print(i)
...
7
6
5
4
3
2
1
If you aren't using it as an index but for something that can have positive or negative values, it still comes in handy:
>>> for i in range(2, -3, -1): print(i)
...
2
1
0
-1
-2
>>> for i in range(-2, 3, 1): print(i)
...
-2
-1
0
1
2
range(1, n+1) is not considered duplication, but I can see that this might become a hassle if you were going to change 1 to another number.
This removes the duplication using a generator:
for _ in (number+1 for number in range(5)):
print(_)
for i in range(n):
print(i+1)
This will output:
1
2
...
n
Not a general answer, but for very small ranges (say, up to five), I find it much more readable to spell them out in a literal:
for _ in [1,2,3]:
print _
That's true even if it does start from zero.
range(1, n+1) is common way to do it, but if you don't like it, you can create your function:
def numbers(first_number, last_number, step=1):
return range(first_number, last_number+1, step)
for _ in numbers(1, 5):
print(_)

Python For loop and range function

def countMe(num):
for i in range(0, num, 3):
print (i)
countMe(18)
def oddsOut(num1, num2):
for i in range(num1):
for j in range(num2):
print(i*j)
oddsOut(3, 8)
I don't understand how the range function works:
in countMe shouldn't the code go up till 18 ;
why is the last number printed in countMe 15, and not 18 ;
why is that in the second function oddsOut the function only counts till 7 for j and not 8 even though j is 8 ;
why is the last number printed in oddsOut 14.
well, from the help:
>>> help(range)
range(...)
range([start,] stop[, step]) -> list of integers
Return a list containing an arithmetic progression of integers.
range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
When step is given, it specifies the increment (or decrement).
For example, range(4) returns [0, 1, 2, 3]. The end point is omitted!
These are exactly the valid indices for a list of 4 elements.
so the last increment is not stop, but the last step before stop.
in countMe shouldn't the code go up till 18 ;
why is the last number printed in countMe 15, and not 18 ;
why is that in the second function oddsOut the function only founts till 7 for j and not 8 even though j is 8 ;
why is the last number printed in oddsOut 14.
more generally speaking the answer to those questions is that in most of the languages, a range is defined as [start:stop[, i.e. the last value of the range is never included, and the indexes start always at 0. The mess being that in a few languages and when working on algorithmics, ranges start at 1 and are inclusive with the last value.
In the end, if you want to include the last value you can do:
def closed_range(start, stop, step=1):
return range(start, stop+1, step)
or in your example:
>>> def countMe(num):
>>> for i in range(0, num+1, 3):
>>> print (i)
>>>
>>> countMe(18)
0
3
6
9
12
15
18
>>>
The stop parameter in a range does not include that number for example
for i in range(0,5):
print i
would print 0-4 but not 5.
Ranges in Python do not include the ending value. This is consistent with slices.
If you need a way to remember this, consider that range(10) has 10 elements - the numbers 0 through 9.

slicing tuple out of index in a form of x[0:4:-1]

x = (1, 2, 3, 4)
Here, x[0:4:-1] gives an empty tuple (). Why is this happening?
I thought it would just give a reversed tuple (4,3,2,1)...
You're asking for all of the values starting at 0, ending before 4, counting by -1 at a time. That's no values.
What you want to do is start at 3, end before -1, counting by -1 at a time. Except… you can't put -1 in there, because that means "1 from the end". So, you have to write "start at 3, end when you've exhausted the whole sequence, counting by -1 at a time", like this:
x[3::-1]
Or, more simply:
x[::-1]
It may help your understanding to turn the slice into an explicit loop. It looks something like this:
def slicify(sequence, start, end, step):
if start < 0: start += len(sequence)
if end < 0: end += len(sequence)
result = []
while (start < end if step > 0 else start > end):
result.append(sequence[start])
start += step
return result
But this is only roughly correct. The exact definition is in the documentation under Sequence Types, under note 5:
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). If i or j is greater than len(s), use len(s). 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.
You'd need to either omit the start and end, or reverse the start and end:
x[::-1] # (4, 3, 2, 1)
x[3:0:-1] # (4, 3, 2)
x[3::-1] # (4, 3, 2, 1)
x[3:-5:-1] # (4, 3, 2, 1)
The end point is not included, so slicing with [3:0:-1] only returns three elements. The last example uses a negative value to be subtracted from the length of the tuple to end up with endpoint -1.
Using a negative stride means Python wants to count backwards, and starting at 0 you'll never get to 4.
Note that the Python slice syntax applies to more than just tuples; strings and lists support the exact same syntax.

Categories

Resources