try:
last_found = -1
for num in L1:
last_found = L2.index(num, last_found + 1)
return True
except ValueError:
return False
The index
method of list L2 returns the position at which the first argument (num
) is found in the list; called, like here, with a second arg, it starts looking in the list at that position. If index
does not find what it's looking for, it raises a ValueError
exception.
So, this code uses this approach to look for each item num
of L1
, in order, inside L2
. The first time it needs to start looking from position 0; each following time, it needs to start looking from the position just after the last one where it found the previous item, i.e. last_found + 1
(so at the start we must set last_found = -1
to start looking from position 0 the first time).
If every item in L1 is found this way (i.e. it's found in L2 after the position where the previous item was found), then the two lists meet the given condition and the code returns True
. If any item of L1 is ever not-found, the code catches the resulting ValueError
exception and just returns False
.
A different approach would be to use iterators over the two lists, that can be formed with the iter
built-in function. You can "advance" an iterator by calling built-in next
on it; this will raise StopIteration
if there is no "next item", i.e., the iterator is exhausted. You can also use for
on the iterator for a somewhat smoother interface, where applicable. The low-level approach using the iter/next idea:
i1 = iter(L1)
i2 = iter(L2)
while True:
try:
lookfor = next(i1)
except StopIteration:
# no more items to look for == all good!
return True
while True:
try:
maybe = next(i2)
except StopIteration:
# item lookfor never matched == nope!
return False
if maybe == lookfor:
break
or, a bit higher-level:
i1 = iter(L1)
i2 = iter(L2)
for lookfor in i1:
for maybe in i2:
if maybe == lookfor:
break
else:
# item lookfor never matched == nope!
return False
# no more items to look for == all good!
return True
In fact, the only crucial use of iter
here is to get i2 -- having the inner loop as for maybe in i2
guarantees the inner loop won't start looking from the beginning every time, but, rather, it will keep looking where it last left off. The outer loop might as well for for lookfor in L1:
, since it has no "restarting" issue.
Key, here, is the else:
clause of loops, which triggers if, and only if, the loop was not interrupted by break
but rather exited naturally.
Working further on this idea we are again reminded of the in
operator, which also can be made to continue where it last left off simply by using an iterator. Big simplification:
i2 = iter(L2)
for lookfor in L1:
if lookfor not in i2:
return False
# no more items to look for == all good!
return True
But now we recognize that is exactly the patter abstracted by the short-circuiting any
and all
built-in "short-circuiting accumulator" functions, so...:
i2 = iter(L2)
return all(lookfor in i2 for lookfor in L1)
which I believe is just about as simple as you can get. The only non-elementary bit left here is: you need to use an iter(L2)
explicitly, just once, to make sure the in
operator (intrinsically an inner loop) doesn't restart the search from the beginning but rather continues each time from where it last left off.