Naming
The iterations
argument is not a great name.
The purpose of the function is to return the n-th element of the sequence, and whether this element is obtained by iterating, or not, is an implementation detail.
Therefore, the argument is best named n
.
Typing
It is useful to the reader to make explicit the types of the arguments and return value of your functions.
In your case:
def look_and_say(n: int) -> int:
pass
Documentation
It is useful to the reader to document what the function is doing. This is generally done using docstrings for a brief explanation.
def look_and_say(n: int) -> int:
"""Returns the `n`th element of the look-and-say sequence starting at `seed`.
For background information see: https://en.wikipedia.org/wiki/Look-and-say_sequence."""
pass
Testing
You should use unit-tests to verify that basic properties hold, as well as to verify edge cases. This can require some setup.
A quick way to get at least a few tests is to use doctests:
def look_and_say(n: int) -> int:
"""Returns the `n`th element of the look-and-say sequence starting at `seed`.
For background information see: https://en.wikipedia.org/wiki/Look-and-say_sequence.
>>> look_and_say(4)
312211
"""
pass
It's particular nice because it also showcases how to use the function to the reader.
Hardcoded seed
If you do not want to hardcode the seed, a good option is to accept the seed as an optional parameter, with a default value:
- Users who wish to use the default seed do not have to specify it.
- Users who wish for a different seed may specify it.
It's all upsides.
Break it down!
I would argue that your function does a bit too much, which in turns may make it harder to test.
Suppose that in the process of testing you realize there's a bug because the output doesn't match the expected number... but where? Is the bug because you're not splitting the string into the appropriate runs? Or not catenating the new string properly? Forgetting a part in some cases?
By breaking down the functionality into elementary pieces, you can test each elementary piece in isolation, ensuring it works as expected, and then assemble all the pieces together.
Generators
Whenever you think of "sequence" in Python, you should think of generators. Generators are the Pythonic way of lazily generating a sequence of elements, one at a time.
The first task, thus, should be about creating a generator, which will generate the sequence of elements for us.
In this specific case, we can sketch our generator like so:
def generate_look_and_say(seed: int) -> Iterator[int]:
"""Returns an iterator of the look-and-say sequence starting with `seed`.
For background information see: https://en.wikipedia.org/wiki/Look-and-say_sequence.
>>> generator = generate_look_and_say(11)
>>> [next(generator) for _ in range(5)]
[11, 21, 1211, 111221, 312211]
"""
# Manipulate the elements as strings, as it's easier.
element = str(seed)
while True:
yield int(element)
element = compute_next_look_and_say(element)
With this structure in place, we just need to define the function to actually compute the new element:
def compute_next_look_and_say(element: str) -> str:
"""Computes the next element of the look-and-say sequence after `element`.
>>> compute_next_look_and_say('111221')
'312211'
"""
return ''.join(f'{len(r)}{r[0]}' for r in generate_runs(element))
def generate_runs(element: str) -> Iterator[str]:
"""Returns an iterator of the runs of equal characters in `element`.
>>> list(generate_runs('312211'))
['3', '1', '22', '11']
"""
assert len(element) > 0
start = 0
while start < len(element):
end = start + 1
while end < len(element) and element[end] == element[start]:
end += 1
yield element[start:end]
start = end
Note that no attempt at optimizing this code was performed. I do hope it's Pythonic.
Using the generator
From then on, it's a simple matter of using the generator to provide the answer:
def look_and_say(n: int, seed: int = 11) -> int:
"""Returns the `n`th element of the look-and-say sequence starting at `seed`.
This is but a convenience function over the iterator returned by `generate_look_and_say`, if you wish to obtain multiple elements of the sequence, using the iterator directly is more efficient.
For background information see: https://en.wikipedia.org/wiki/Look-and-say_sequence.
>>> look_and_say(4)
312211
"""
for i, element in enumerate(generate_look_and_say(seed)):
if i == n:
return element