Negative subscripts

dn PythonList at DancesWithMice.info
Sat Nov 27 03:20:15 EST 2021


On 27/11/2021 19.11, Frank Millman wrote:
> On 2021-11-26 11:24 PM, dn via Python-list wrote:
>> On 26/11/2021 22.17, Frank Millman wrote:
>>> In my program I have a for-loop like this -
>>>
>>>>>> for item in x[:-y]:
>>> ...    [do stuff]
>>>
>>> 'y' may or may not be 0. If it is 0 I want to process the entire list
>>> 'x', but of course -0 equals 0, so it returns an empty list.
>> ...
>>
> 
> [...]
> 
> That was an interesting read - thanks for spelling it out.
> 
>>
>>>>> for y in [ 0, 1, 2, 3, 4, 5 ]:
>> ...     print( y, x[ :len( x ) - y ] )
>> ...
>> 0 ['a', 'b', 'c', 'd', 'e']
>> 1 ['a', 'b', 'c', 'd']
>> 2 ['a', 'b', 'c']
>> 3 ['a', 'b']
>> 4 ['a']
>> 5 []
>>
>> and yes, if computing y is expensive/ugly, for extra-credit, calculate
>> the 'stop' value outside/prior-to the for-loop!
>>
> 
> Ignoring the 'ugly' for the moment, what if computing y is expensive?
> 
> To check this, I will restate the example to more closely match my use
> case.
> 
>>>> x = [1, 2, 3, 4, 5, 6, 7]
>>>> y = [5, 4, 3]
>>>> z = []
>>>>
>>>> for i in x[ : len(x) - len(y) ]:
> ...   i
> ...
> 1
> 2
> 3
> 4
>>>>
>>>> for i in x[ : len(x) - len(z) ]:
> ...   i
> ...
> 1
> 2
> 3
> 4
> 5
> 6
> 7
>>>>
> 
> So it works perfectly (not that I had any doubts).
> 
> But what if it is expensive to compute y? Or to rephrase it, is y
> computed on every iteration, or only on the first one?
> 
> Without knowing the internals, it is not possible to tell just by
> looking at it. But there is a technique I learned from Peter Otten
> (haven't heard from him for a while - hope he is still around).
> 
>>>> def lng(lst):
> ...   print(f'*{len(lst)}*')
> ...   return len(lst)
> ...
>>>>
>>>> for i in x[ : lng(x) - lng(y) ]:
> ...   i
> ...
> *7*
> *3*
> 1
> 2
> 3
> 4
>>>>
>>>> for i in x[ : lng(x) - lng(z) ]:
> ...   i
> ...
> *7*
> *0*
> 1
> 2
> 3
> 4
> 5
> 6
> 7
>>>>
> 
> From this it is clear that y is only computed once, when the loop is
> started. Therefore I think it follows that there is no need to
> pre-compute y.
> 
> Hope this makes sense.

Perfect sense (== @Peter sense).


To confirm:

«8.3. The for statement¶

The for statement is used to iterate over the elements of a sequence
(such as a string, tuple or list) or other iterable object:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

The expression list is evaluated once; it should yield an iterable
object. An iterator is created for the result of the expression_list.
The suite is then executed once for each item provided by the iterator,
in the order returned by the iterator. Each item in turn is assigned to
the target list using the standard rules for assignments (see Assignment
statements), and then the suite is executed. When the items are
exhausted (which is immediately when the sequence is empty or an
iterator raises a StopIteration exception), the suite in the else
clause, if present, is executed, and the loop terminates.
»
https://docs.python.org/3/reference/compound_stmts.html#the-for-statement


That said, I'm wondering if all is strictly true when the
expression_list is a generator, which by definition features
lazy-execution rather than up-front list production. So, things may
depend upon the full-nature of the application.


Assuming, as-per these simple examples, there is no advantage to
pre-computation, it may be more readable to 'prepare' the control-values
separately, thereby simplifying the (appearance of the) for-statement.


The problem at this level, are the terms: "fairly long", "pretty ugly"*,
and "expensive". There are various tactics which might be brought to
bear, but will only be applicable in specific situations. So, too little
information, too much speculation...

For example if there is repetition inside the loop, where a particular
x[ i ] is related to something multiple times - perhaps in  multiple
executions of the loop, 'memoisation' may save recalculation -
particularly if this happens inside a nested-loop. This has nothing to
do with the for-loop itself. It trades memory-space for execution-time -
one "expense" for another. However, only you can tell if it, or any
other idea, might be relevant in this application...


* can you make up your mind? Is it pretty, or ugly?
-- 
Regards,
=dn


More information about the Python-list mailing list