|
| 1 | +""" |
| 2 | +Look-and-Say Sequence |
| 3 | +===================== |
| 4 | +The Look-and-Say sequence is a sequence of integers where each term is |
| 5 | +generated by reading the digits of the previous term aloud. |
| 6 | +
|
| 7 | +Starting from "1": |
| 8 | + 1 -> "one 1" -> 11 |
| 9 | + 11 -> "two 1s" -> 21 |
| 10 | + 21 -> "one 2, one 1" -> 1211 |
| 11 | + 1211 -> "one 1, one 2, two 1s" -> 111221 |
| 12 | + ... |
| 13 | +
|
| 14 | +Reference: https://en.wikipedia.org/wiki/Look-and-say_sequence |
| 15 | +
|
| 16 | +Examples: |
| 17 | +>>> list(look_and_say_sequence(1)) |
| 18 | +['1'] |
| 19 | +>>> list(look_and_say_sequence(5)) |
| 20 | +['1', '11', '21', '1211', '111221'] |
| 21 | +>>> next_term('1') |
| 22 | +'11' |
| 23 | +>>> next_term('111221') |
| 24 | +'312211' |
| 25 | +>>> next_term('') |
| 26 | +Traceback (most recent call last): |
| 27 | + ... |
| 28 | +ValueError: Input term must be a non-empty string of digits. |
| 29 | +""" |
| 30 | + |
| 31 | +from itertools import groupby |
| 32 | + |
| 33 | + |
| 34 | +def next_term(term: str) -> str: |
| 35 | + """ |
| 36 | + Generate the next term in the Look-and-Say sequence. |
| 37 | +
|
| 38 | + Each group of consecutive identical digits is replaced by |
| 39 | + the count of digits followed by the digit itself. |
| 40 | +
|
| 41 | + Args: |
| 42 | + term: A non-empty string of digits representing the current term. |
| 43 | +
|
| 44 | + Returns: |
| 45 | + The next term in the sequence as a string. |
| 46 | +
|
| 47 | + Raises: |
| 48 | + ValueError: If the input term is empty. |
| 49 | +
|
| 50 | + Examples: |
| 51 | + >>> next_term('1') |
| 52 | + '11' |
| 53 | + >>> next_term('11') |
| 54 | + '21' |
| 55 | + >>> next_term('21') |
| 56 | + '1211' |
| 57 | + >>> next_term('1211') |
| 58 | + '111221' |
| 59 | + >>> next_term('111221') |
| 60 | + '312211' |
| 61 | + """ |
| 62 | + if not term: |
| 63 | + raise ValueError("Input term must be a non-empty string of digits.") |
| 64 | + return "".join(str(len(list(group))) + digit for digit, group in groupby(term)) |
| 65 | + |
| 66 | + |
| 67 | +def look_and_say_sequence(num_terms: int, start: str = "1"): |
| 68 | + """ |
| 69 | + Generate the Look-and-Say sequence up to num_terms terms. |
| 70 | +
|
| 71 | + Args: |
| 72 | + num_terms: The number of terms to generate (must be >= 1). |
| 73 | + start: The starting term (default is "1"). |
| 74 | +
|
| 75 | + Yields: |
| 76 | + Each term of the sequence as a string. |
| 77 | +
|
| 78 | + Raises: |
| 79 | + ValueError: If num_terms is less than 1. |
| 80 | +
|
| 81 | + Examples: |
| 82 | + >>> list(look_and_say_sequence(1)) |
| 83 | + ['1'] |
| 84 | + >>> list(look_and_say_sequence(5)) |
| 85 | + ['1', '11', '21', '1211', '111221'] |
| 86 | + >>> list(look_and_say_sequence(6)) |
| 87 | + ['1', '11', '21', '1211', '111221', '312211'] |
| 88 | + >>> list(look_and_say_sequence(0)) |
| 89 | + Traceback (most recent call last): |
| 90 | + ... |
| 91 | + ValueError: num_terms must be a positive integer. |
| 92 | + """ |
| 93 | + if num_terms < 1: |
| 94 | + raise ValueError("num_terms must be a positive integer.") |
| 95 | + term = start |
| 96 | + for _ in range(num_terms): |
| 97 | + yield term |
| 98 | + term = next_term(term) |
| 99 | + |
| 100 | + |
| 101 | +if __name__ == "__main__": |
| 102 | + import doctest |
| 103 | + |
| 104 | + doctest.testmod() |
| 105 | + |
| 106 | + print("Look-and-Say Sequence (first 8 terms):") |
| 107 | + for i, term in enumerate(look_and_say_sequence(8), start=1): |
| 108 | + print(f" Term {i}: {term}") |
0 commit comments