In function where we need to search for the best element according to a condition (like Min Max functions: search the min or the max), I saw some (or many) peoples check if the variable that hold the best answer is None inside the loop side by side of the condition checking.
To illustrate the point, let the following 2 source code:
A) use the test is None
inside the loop:
list_vals = [10,9,8,7,6,5,4,3,2,1]
min_val = None
for val in list_vals:
if min_val is None or val<min_val:
min_val = val
print("min = ", min_val)
B) The best recive the first element
list_vals = [10,9,8,7,6,5,4,3,2,1]
min_val = list_val[0]
for val in list_vals:
if val<min_val:
min_val = val
print("min = ", min_val)
My Questions are:
To see for this example if the execution time is similaire or not, I computed the time for both method A and B with
nb = 100000000 # 100_000_000
list_vals = random.sample(range(nb), nb)
I repeated the test with other length, and the result is the same, the B method is alyse (relatively) faster than the A method.
Thank you for your help.
Edit one: let assume that list_vals is not empty. Thank you @0x5453
In general code, you may want to work with arbitrary iterables, not just list
, in which case only the first form works at all (you can't index non-sequences). You may also need to handle potentially empty inputs (A handles them by producing None
, while B raises an IndexError
). It does add non-zero cost (CPython has only the simplest, most local of optimizers; it can't make broad deductions like "min_val
is only None
on the first loop"). If performance is critical, you can get the best of both worlds (though with slightly uglier code) this way:
vals = ... could be any iterable ...
iter_vals = iter(vals) # Explicitly convert to iterator (if already an iterator, just returns vals at trivial cost)
min_val = next(iter_vals, None) # Pulls first value, or None if vals was empty
for val in iter_vals: # Iterates rest of values looking for minimum
if val < min_val:
min_val = val
print("min = ", min_val)
In this scenario, you don't make assumptions about the input being a sequence, you make no unnecessary copies of data (which slicing list_vals[1:]
would do), you don't have to invent a sentinel value for the initial case, because you can safely acquire the first element once, and you don't test the initial value against itself (because stateful iterators only produce the initial value once).