for...else
I recommend that you study the for...else
semantic. The comment by @mkrieger1 is a maybe a bit concise.
The doc states:
a loop’s else clause runs when no break occurs.
For instance:
>>> for i in range(1):
... print("first and only iteration")
... break
... else: # not executed since there is a break
... print("no break was met")
first and only iteration
But:
>>> for i in range(1):
... print("first and only iteration")
... else: # executed since the loop exits normally
... print("no break was met")
first and only iteration
no break was met
If no break ever happens in the body of the loop, the else clause always run,
and thus the following pieces of code are equivalent:
for ...:
<loop body>
else:
<else statement>
And:
for ...:
<loop body>
<else statement>
A list of lists of lists or a list of lists?
You write:
chain_steps = []
for chain_ in trip_chains:
for stepscount_ in range(len(chain_)-1):
chain_steps.append([chain_[stepscount_], chain_[stepscount_+1]])
else:
chain_steps.append([chain_[-1], chain_[0]])
As @Ture Pålsson write, thats only produces a list of lists. The correct version would be:
trip_chains = [['London', 'Paris', 'Madrid'],
['Delhi', 'London', 'New York'],
['Jerusalem', 'Cairo', 'Paris']]
chain_steps = []
for chain_ in trip_chains:
chain_step = [] # create a new list in the outer loop
# and fill it in the inner loop
for stepscount_ in range(len(chain_)-1):
chain_step.append([chain_[stepscount_], chain_[stepscount_+1]])
chain_step.append([chain_[-1], chain_[0]])
chain_steps.append(chain_step)
from pprint import pprint
pprint(chain_steps)
Output:
[[['London', 'Paris'], ['Paris', 'Madrid'], ['Madrid', 'London']],
[['Delhi', 'London'], ['London', 'New York'], ['New York', 'Delhi']],
[['Jerusalem', 'Cairo'], ['Cairo', 'Paris'], ['Paris', 'Jerusalem']]]
No "elementary math" is needed.
Can we add an else
clause in a list comprehension?
The main question is: can we have a break
in a list comprehension? The answer is no (Python 3):
>>> [i if i<5 else break for i in range(10)]
Traceback (most recent call last):
[i if i<5 else break for i in range(10)]
^
SyntaxError: invalid syntax
(See Using break in a list comprehension for more details).
Hence, the else
clause has no meaning in a list comprehension. Consequence: we cannot have an else
clause in a list comprehension.
Can we add an extra element in a list comprehension?
I will focus on the inner loop:
chain_ = ['London', 'Paris', 'Madrid']
chain_step = []
for stepscount_ in range(len(chain_)-1):
chain_step.append([chain_[stepscount_], chain_[stepscount_+1]])
chain_step.append([chain_[-1], chain_[0]]) # extra element
pprint(chain_step)
Output:
[['London', 'Paris'], ['Paris', 'Madrid'], ['Madrid', 'London']]
Obviously, we can add an iteration with the last element:
chain_step = []
for stepscount_ in list(range(len(chain_)-1))+[-1]:
chain_step.append([chain_[stepscount_], chain_[stepscount_+1]])
And then:
>>> chain_ = ['London', 'Paris', 'Madrid']
>>> [[chain_[s], chain_[s+1]] for s in list(range(len(chain_)-1))+[-1]]
[['London', 'Paris'], ['Paris', 'Madrid'], ['Madrid', 'London']]
That's a bit overkill when you can write:
>>> chain_ = ['London', 'Paris', 'Madrid']
>>> [[chain_[s], chain_[s+1]] for s in range(len(chain_)-1)] + [[chain_[-1], chain_[0]]]
[['London', 'Paris'], ['Paris', 'Madrid'], ['Madrid', 'London']]
(You can easily wrap this in an outer list comprehension:
chain_steps = [
[[chain_[s], chain_[s+1]] for s in range(len(chain_)-1)] + [[chain_[-1], chain_[0]]]
for chain_ in trip_chains
]
)
Bonus: is there a cleaner way to write this?
YES (inner loop):
chain_step = []
for step in zip(chain_, chain_[1:]+[chain_[0]]):
chain_step.append(step)
pprint(chain_step)
Output:
[('London', 'Paris'), ('Paris', 'Madrid'), ('Madrid', 'London')]
The zip
function will pair the elements:
chain[0], chain[1], ... chain_[n-2], chain_[n-1] <- chain_
chain[1], chain[2], ... chain_[n-1], chain_[0] <- chain_[1:]+[chain_[0]]
As you see, you have a list of tuples, not a list of lists. That's better, because a step is a pair, not a list (a tuple size won't change, a list size might change).
Hence the final double list comprehension:
>>> trip_chains = [['London', 'Paris', 'Madrid'],
... ['Delhi', 'London', 'New York'],
... ['Jerusalem', 'Cairo', 'Paris']]
>>> [[step for step in zip(chain_, chain_[1:]+[chain_[0]])] for chain_ in trip_chains]
[[('London', 'Paris'), ('Paris', 'Madrid'), ('Madrid', 'London')], [('Delhi', 'London'), ('London', 'New York'), ('New York', 'Delhi')], [('Jerusalem', 'Cairo'), ('Cairo', 'Paris'), ('Paris', 'Jerusalem')]]
Or, because the inner list comprehension just takes the elements of zip
:
>>> [list(zip(chain_, chain_[1:]+[chain_[0]])) for chain_ in trip_chains]
[[('London', 'Paris'), ('Paris', 'Madrid'), ('Madrid', 'London')], [('Delhi', 'London'), ('London', 'New York'), ('New York', 'Delhi')], [('Jerusalem', 'Cairo'), ('Cairo', 'Paris'), ('Paris', 'Jerusalem')]]
Beware: the code becomes hard to maintain...