Skip to content

Instantly share code, notes, and snippets.

@sashadev-sky
Last active March 19, 2022 11:03
Show Gist options
  • Save sashadev-sky/cfc66efa78193182c5bb3c1461133683 to your computer and use it in GitHub Desktop.
Save sashadev-sky/cfc66efa78193182c5bb3c1461133683 to your computer and use it in GitHub Desktop.
Enumerating over a list with both the current and next value in Python
value = [3, 4, 5, 6]
longer_value = [3, 4, 5, 6, 8]
iter_value = iter(value)

print([*value])
# >>> [3, 4, 5, 6]
print([*value]*2)
# >>> [3, 4, 5, 6, 3, 4, 5, 6]
print([*iter(value)]*2)
# >>> [3, 4, 5, 6, 3, 4, 5, 6]
print([*iter_value]*2)
# >>> [3, 4, 5, 6, 3, 4, 5, 6]

[print(x) for x in [*iter(value)]*2]
# >>> 3
# >>> 4
# >>> 5
# >>> 6
# >>> 3
# >>> 4
# >>> 5
# >>> 6

# Don't need iter above
[print(x) for x in [*value]*2]
# >>> 3
# >>> 4
# >>> 5
# >>> 6
# >>> 3
# >>> 4
# >>> 5
# >>> 6

# Wouldn't in reality do ex. right above - with an array can just multiply by a number, for ex:
[print(i) for i in value*2].
# but you can't multiply iters by numbers:
[print(i) for i in iter(value)*2] # >>> TypeError: unsupported operand type(s) for *: 'list_iterator' and 'int'

# So what's the iter for? See a difference here, with zip:

[print(x) for x in zip(*[value]*2)]
# >>> (3, 3)
# >>> (4, 4)
# >>> (5, 5)
# >>> (6, 6)

[print(x) for x in zip(*[iter(value)]*2)]
# >>> (3, 4)
# >>> (5, 6)

# Why does this happen?

# Refresher on using list multiples to repeat item - 
# it's the same iterator twice:

print(id(value))  # >>> 139774155992640
print([id(x) for x in [value]*2])  # >>> [139774155992640, 139774155992640]
print(id(iter(value)))  # >>> 140205403918096
print([id(x) for x in [iter(value)]*2])  # >>> [140205403918096, 140205403918096]

# Without starred expression, zip would only see single n-item list:

print([iter(value)]*2) # >>> [<list_iterator object at 0x7fbc4f96ff10>, <list_iterator object at 0x7fbc4f96ff10>]

# Must use starred expression to expand n arguments

print(*[iter(value)]*2) # >>> <list_iterator object at 0x7fbc4f96ff10> <list_iterator object at 0x7fbc4f96ff10>

# By repeating the same iterator, n-times,
# each pass of zip will call the same iterator.__next__() n times.
# This is equivalent to manually calling __next__() until complete

print((iter_value.__next__(), iter_value.__next__()))
# >>> (3, 4)
print((iter_value.__next__(), iter_value.__next__()))
# >>> (5, 6)
print((iter_value.__next__(), iter_value.__next__()))
# >>> Traceback (most recent call last):
#      File "<stdin>", line 1, in <module>
#    StopIteration

# Continuing with same iterator multiple times in list

print(zip(*[iter(value)]*2))
# >>> <zip object at 0x7f36bd856dc0>
print(list(zip(*[iter(value)]*2)))
# >>> [(3, 4), (5, 6)]

# We must use list multiples. Explicit listing creates 2 unique iterators

print([iter(value)]*2 == [iter(value), iter(value)])
# False
[print(x) for x in zip(*[iter(value), iter(value)])]
# >>> (3, 3)
# >>> (4, 4)
# >>> (5, 5)
# >>> (6, 6)

# It is also very important to note that unpaired elements will not be used at all, and
# this will not throw any sort of error:

[print(x) for x in zip(*[iter(value), iter(longer_value)])]
# >>> (3, 3)
# >>> (4, 4)
# >>> (5, 5)
# >>> (6, 6)


# So in summary, the trick is that it is the same iterator twice, so when __next__
# is called on it twice, it gives the current and next element. A regular list does
# not have the same interface - they're just re-indexed, resulting in getting the same
# 2 values.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment