diff --git a/exercises/practice/roman-numerals/.approaches/config.json b/exercises/practice/roman-numerals/.approaches/config.json index d824f6dd801..cadc8c3acf8 100644 --- a/exercises/practice/roman-numerals/.approaches/config.json +++ b/exercises/practice/roman-numerals/.approaches/config.json @@ -20,7 +20,7 @@ "uuid": "4ed8396c-f2c4-4072-abc9-cc8fe2780a5a", "slug": "loop-over-romans", "title": "Loop Over Romans", - "blurb": "Test Roman Numerals from the largest down and eat the maximum possible at each step.", + "blurb": "Test Roman numerals from the largest down and eat the maximum possible at each step.", "authors": [ "BethanyG", "colinleach" diff --git a/exercises/practice/roman-numerals/.approaches/if-else/content.md b/exercises/practice/roman-numerals/.approaches/if-else/content.md index 798075f1fe9..7d610361485 100644 --- a/exercises/practice/roman-numerals/.approaches/if-else/content.md +++ b/exercises/practice/roman-numerals/.approaches/if-else/content.md @@ -14,27 +14,27 @@ def roman(number): res = '' if m > 0: - res += m * 'M' + res += m * 'M' if 4 > c > 0: - res += c * 'C' + res += c * 'C' elif c == 4: - res += 'CD' + res += 'CD' elif 9 > c > 4: - res += 'D' + ((c - 5) * 'C') + res += 'D' + ((c - 5) * 'C') elif c == 9: - res += 'CM' + res += 'CM' - if 4 > x > 0: + if 4 > x > 0: res += x * 'X' elif x == 4: res += 'XL' elif 9 > x > 4: res += 'L' + ((x - 5) * 'X') elif x == 9: - res += 'XC' + res += 'XC' - if 4 > i > 0: + if 4 > i > 0: res += i * 'I' elif i == 4: res += 'IV' @@ -61,19 +61,16 @@ This can be done with a list comprehension, left-padding with zeros as necessary digits = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] ``` -The blocks for hundreds, tens and units are all essentially the same, so we can put that code in a function. +The blocks for hundreds, tens, and units are all essentially the same, so we can put that code in a function. We just need to pass in the digit, plus a tuple of translations for `(1, 4, 5, 9)` or their 10x and 100x equivalents. It is also unnecessary to keep retesting the lower bounds within an `elif`, as the code line will only be reached if that is satisfied. -Using `return` instead of `elif` is a matter of personal preference. Given that, the code simplifies to: ```python def roman(number: int) -> str: def translate_digit(digit: int, translations: iter) -> str: - assert isinstance(digit, int) and 0 <= digit <= 9 - units, four, five, nine = translations if digit < 4: return digit * units @@ -83,7 +80,6 @@ def roman(number: int) -> str: return five + (digit - 5) * units return nine - assert isinstance(number, int) m, c, x, i = ([0, 0, 0, 0] + [int(d) for d in str(number)])[-4:] res = '' @@ -99,5 +95,6 @@ def roman(number: int) -> str: return res ``` -The last few lines are quite similar and it would be possible to refactor them into a loop, but this is enough to illustrate the principle. +(Using `return` instead of `elif` is a matter of personal preference.) +The last few lines are quite similar and it would be possible to refactor them into a loop, but this is enough to illustrate the principle. diff --git a/exercises/practice/roman-numerals/.approaches/introduction.md b/exercises/practice/roman-numerals/.approaches/introduction.md index 3358c23f40e..05360bd86a3 100644 --- a/exercises/practice/roman-numerals/.approaches/introduction.md +++ b/exercises/practice/roman-numerals/.approaches/introduction.md @@ -9,29 +9,28 @@ In the version used for this exercise, the longest string needed to represent a Minor variants of the system have been used which represent 4 as IIII rather than IV, allowing for longer strings, but those are not relevant here. The system is inherently decimal: the number of human fingers has not changed since ancient Rome, nor the habit of using them for counting. -However, there is no zero value available, so Roman numerals represent powers of 10 with different letters (I, X, C, M), not by position (1, 10, 100, 1000). +However, there is no zero value available, so Roman numerals represent powers of 10 with different letters (I, X, C, and M), not by position (1, 10, 100, 1000, etc). The approaches to this exercise break down into two groups, with many variants in each: + 1. Split the input number into digits, and translate each separately. 2. Iterate through the Roman numbers, from large to small, and convert the largest valid number at each step. ## Digit-by-digit approaches -The concept behind this class of approaches: -1. Split the input number into decimal digits. -2. For each digit, get the Roman equivalent and append to a list. -3. Join the list into a string and return it. +The process behind this class of approaches: + +1. Split the input number into decimal digits. +2. For each digit, get the Roman equivalent and append to a list. +3. Join the list into a string and return it. + Depending on the implementation, there may need to be a list-reverse step. ### With `if` conditions ```python def roman(number: int) -> str: - assert isinstance(number, int) - def translate_digit(digit: int, translations: iter) -> str: - assert isinstance(digit, int) and 0 <= digit <= 9 - units, four, five, nine = translations if digit < 4: return digit * units @@ -54,44 +53,44 @@ def roman(number: int) -> str: return res ``` -See [`if-else`][if-else] for details. +See the [`if-else`][if-else] approach for details. ### With table lookup ```python def roman(number): - assert (number > 0) - # define lookup table (as a tuple of tuples, in this case) table = ( - ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), - ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), - ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), - ("M", "MM", "MMM")) + ('I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'), + ('X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'), + ('C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'), + ('M', 'MM', 'MMM')) # convert the input integer to a list of single digits digits = [int(d) for d in str(number)] - # we need the row in the lookup table for our most-significant decimal digit - inverter = len(digits) - 1 + # get the row in the lookup table for the most-significant decimal digit + inverter = len(digits) - 1 # translate decimal digits list to Roman numerals list - roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + roman_digits = [table[inverter - i][d - 1] for i, d in enumerate(digits) if d != 0] # convert the list of Roman numerals to a single string return ''.join(roman_digits) ``` -See [`table-lookup`][table-lookup] for details. +See the [`table-lookup`][table-lookup] approach for details. ## Loop over Romans approaches In this class of approaches we: -1. Create a mapping from Roman to Arabic numbers, in some suitable format. (_`dicts` or `tuples` work well_) -2. Iterate nested loops, a `for` and a `while`, in either order. -3. At each step, append the largest possible Roman number to a list and subtract the corresponding value from the number being converted. -4. When the number being converted drops to zero, join the list into a string and return it. + +1. Create a mapping from Roman to Arabic numbers, in some suitable format. (_`dicts` or `tuples` work well._) +2. Iterate nested loops, a `for` and a `while`, in either order. +3. At each step, append the largest possible Roman number to a list and subtract the corresponding value from the number being converted. +4. When the number being converted drops to zero, join the list into a string and return it. + Depending on the implementation, there may need to be a list-reverse step. This is one example using a dictionary: @@ -105,7 +104,7 @@ def roman(number: int) -> str: result = '' while number: for arabic in ROMAN.keys(): - if number >= arabic: + if number >= arabic: result += ROMAN[arabic] number -= arabic break @@ -113,7 +112,7 @@ def roman(number: int) -> str: ``` There are a number of variants. -See [`loop-over-romans`][loop-over-romans] for details. +See the [`loop-over-romans`][loop-over-romans] approach for details. ## Other approaches @@ -152,7 +151,7 @@ This is a recursive version of the `loop-over-romans` approach, which only works ```python ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) -ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") +ROMAN_NUM = ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I') def roman(number: int) -> str: return roman_recur(number, 0, []) @@ -167,7 +166,7 @@ def roman_recur(num: int, idx: int, digits: list[str]): return roman_recur(num, idx + 1, digits) ``` -See [`recurse-match`][recurse-match] for details. +See the [`recurse-match`][recurse-match] approach for details. ### Over-use a functional approach @@ -176,7 +175,7 @@ See [`recurse-match`][recurse-match] for details. def roman(number): return ''.join(one*digit if digit<4 else one+five if digit==4 else five+one*(digit-5) if digit<9 else one+ten for digit, (one,five,ten) - in zip([int(d) for d in str(number)], ["--MDCLXVI"[-i*2-1:-i*2-4:-1] for i in range(len(str(number))-1,-1,-1)])) + in zip([int(d) for d in str(number)], ['--MDCLXVI'[-i*2-1:-i*2-4:-1] for i in range(len(str(number))-1,-1,-1)])) ``` *This is Python, but not as we know it*. diff --git a/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md index 95b820ec1b0..6609e7f40ff 100644 --- a/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md +++ b/exercises/practice/roman-numerals/.approaches/itertools-starmap/content.md @@ -4,10 +4,10 @@ from itertools import starmap def roman(number: int) -> str: - orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] - options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] + orders = [(1000, 'M '), (100, 'CDM'), (10, 'XLC'), (1, 'IVX')] + options = lambda I, V, X: ['', I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X] compute = lambda n, chars: options(*chars)[number % (n * 10) // n] - return "".join(starmap(compute, orders)) + return ''.join(starmap(compute, orders)) ``` This approach is certainly concise and ingenious, though it takes functional programming to a level that some Python programmers might consider a little cryptic. @@ -24,7 +24,7 @@ The underlying reason is that lambdas are intended to be anonymous functions emb Internally, they are all given the same name ``, which can greatly complicate debugging. Their use is a particular bugbear of the Python track maintainer. -We can refactor the code to satisfy the linter by using named `def` statements, and lowercase argument names. +We can refactor the code to satisfy the linter by using named `def` statements and lowercase argument names. Type hints are also added for documentation: ```python @@ -33,21 +33,21 @@ from itertools import starmap def roman(number: int) -> str: def options(i: str, v: str, x: str): - return ["", i, i * 2, i * 3, i + v, v, v + i, v + i * 2, v + i * 3, i + x] + return ['', i, i * 2, i * 3, i + v, v, v + i, v + i * 2, v + i * 3, i + x] def compute(n: int, chars: str) -> iter: return options(*chars)[number % (n * 10) // n] - orders = [(1000, "M "), (100, "CDM"), (10, "XLC"), (1, "IVX")] - return "".join(starmap(compute, orders)) + orders = [(1000, 'M '), (100, 'CDM'), (10, 'XLC'), (1, 'IVX')] + return ''.join(starmap(compute, orders)) ``` ## Analysis The central concept is that Roman letters are defined for 1, 5 and 10, times various powers of 10. -`orders` is relatively straightforward: a list of tuples, with each tuple containing the powers of 10 and (as far as possible) the letters for that number times (1, 5, 10). -Roman numerals for 5,000 and 10,000 are not defined, so are replaced here by spaces. +`orders` is relatively straightforward: a list of tuples, with each tuple containing the powers of 10 and (as far as possible) the letters for that number times (1, 5, 10). +Roman numerals for 5,000 and 10,000 are not defined, so spaces are used here instead. The `options()` function just takes the three letters from one of these tuples and returns a list of numerals that can be constructed from them. For example, the 10 to 90 range: @@ -60,15 +60,15 @@ options('X', 'L', 'C') There is no zero, so that is replaced by an empty string. The `compute()` function takes a tuple from `orders` plus the top-level parameter `number`, and converts the appropriate decimal digit to its Roman equivalent, returning an `iterator`. -For example the first digit of 723: +For example, the first digit of 723: ```python number = 723 -[x for x in compute(100, "CDM")] +[x for x in compute(100, 'CDM')] # => ['D', 'C', 'C'] ``` -The `starmap()` function ties `orders`, `options()` and `compute` together, splitting up strings and tuples as necessary to give each function the parameters it needs. +The `starmap()` function ties `orders`, `options()`, and `compute()` together, splitting up strings and tuples as necessary to give each function the parameters it needs. Again, an iterator is returned: ```python @@ -79,7 +79,7 @@ number = 723 Finally, `''.join()` converts this iterator to a single string that can be returned as the desired answer. -Once we get past the deliberate obfuscation, it is quite an elegant approach. +Once we get past the deliberate obfuscation, it is quite an elegant approach. Though perhaps not the most idiomatic Python. ## Credit diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md index 6e1d4cc847b..cded1e44ba5 100644 --- a/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/content.md @@ -9,52 +9,48 @@ def roman(number: int) -> str: result = '' while number: for arabic in ROMAN.keys(): - if number >= arabic: + if number >= arabic: result += ROMAN[arabic] number -= arabic break return result ``` -This approach is one of a family, using some mapping from Arabic (decimal) to Roman numbers. +This approach is one of a family, using some mapping from Arabic (decimal) numbers to Roman numbers. The code above uses a dictionary. With minor changes, we could also use nested tuples: ```python -ROMANS = ((1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), - (90, "XC"), (50, "L"), (40, "XL"), (10, "X"), - (9, "IX"), (5, "V"), (4, "IV"), (1, "I")) +ROMANS = ((1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), + (90, 'XC'), (50, 'L'), (40, 'XL'), (10, 'X'), + (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')) def roman(number: int) -> str: - assert(number > 0) - - roman_num = "" - for (k, v) in ROMANS: - while k <= number: - roman_num += v - number -= k + roman_num = '' + for arabic, roman in ROMANS: + while arabic <= number: + roman_num += roman + number -= arabic return roman_num ``` -Using a pair of lists is also possible, with a shared index from the `enumerate()`. +Using a pair of lists is also possible, with a shared index from `enumerate()`. ```python -# Use a translation +# Use a translation numbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1] -names = [ 'M', 'CM','D','CD', 'C','XC','L','XL', 'X','IX','V','IV', 'I'] +names = [ 'M', 'CM','D','CD', 'C','XC','L','XL', 'X','IX','V','IV', 'I'] def roman(number: int) -> str: - "Take a decimal number and return Roman Numeral Representation" - # List of Roman symbols res = [] while (number > 0): # Find the largest amount we can chip off - for i, val in enumerate(numbers): - if (number >= val): - res.append(names[i]) + for idx, val in enumerate(numbers): + if number >= val: + res.append(names[idx]) number -= val break @@ -63,15 +59,14 @@ def roman(number: int) -> str: However, for a read-only lookup it may be better to use (immutable) tuples for `numbers` and `names`. -As Roman numerals are built up from letters for 1, 5, 10 times powers of 10, it is possible to shorten the lookup and build up most of the digits programmatically: +As Roman numerals are built up from letters for 1, 5, and 10 times powers of 10, it is possible to shorten the lookup and build up most of the digits programmatically: ```python -# The 10's, 5's and 1's position chars for 1, 10, 100, 1000. -DIGIT_CHARS = ["XVI", "CLX", "MDC", "??M"] +# The 10's, 5's, and 1's position chars for 1, 10, 100, and 1000. +DIGIT_CHARS = ['XVI', 'CLX', 'MDC', '??M'] def roman(number: int) -> str: - """Return the Roman numeral for a number.""" # Generate a mapping from numeric value to Roman numeral. mapping = [] for position in range(len(DIGIT_CHARS) - 1, -1, -1): @@ -86,7 +81,7 @@ def roman(number: int) -> str: mapping.append((4 * scale, chars[2] + chars[1])) mapping.append((1 * scale, chars[2])) - out = "" + out = '' for num, numerals in mapping: while number >= num: out += numerals @@ -94,7 +89,7 @@ def roman(number: int) -> str: return out ``` -The code below is doing something similar to the dictionary approach at the top of this page, but more concisely: +The code below does something similar to the dictionary approach at the top of this page, but more concisely: ```python def roman(number: int) -> str: @@ -109,17 +104,17 @@ def roman(number: int) -> str: These five solutions all share some common features: + - Some sort of translation lookup. -- Nested loops, a `while`and a `for`, in either order. +- Nested loops, a `while` and a `for`, in either order (except the last one). - At each step, find the largest number that can be subtracted from the decimal input and appended to the Roman representation. When building a string gradually, it is often better to build an intermediate list, then do a `join()` at the end, as in the third example. -This is because strings are immutable, so need to be copied at each step, and the old strings need to be garbage-collected. +This is because strings are immutable, so they need to be copied at each step, and the old strings need to be garbage-collected. However, Roman numerals are always so short that the difference is minimal in this case. Incidentally, notice the use of type hints: `def roman(number: int) -> str`. -This is optional in Python and (currently) ignored by the interpreter, but is useful for documentation purposes. - -Increasingly, IDE's such as VSCode and PyCharm understand the type hints, using them to flag problems and provide advice. +This is optional in Python and is (currently) ignored by the interpreter, but is useful for documentation purposes. +Increasingly, code editors and IDEs such as VSCode and PyCharm understand the type hints, using them to flag problems and provide advice. diff --git a/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt index 2ce6c141c2c..db9baa42e9c 100644 --- a/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt +++ b/exercises/practice/roman-numerals/.approaches/loop-over-romans/snippet.txt @@ -1,8 +1,7 @@ def roman(number): - assert(number > 0) - roman_num = "" - for (k, v) in ROMANS: - while k <= number: - roman_num += v - number -= k + roman_num = '' + for arabic, roman in ROMANS: + while arabic <= number: + roman_num += roman + number -= arabic return roman_num diff --git a/exercises/practice/roman-numerals/.approaches/recurse-match/content.md b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md index 3e0bb0ca5f4..14b97ef1e87 100644 --- a/exercises/practice/roman-numerals/.approaches/recurse-match/content.md +++ b/exercises/practice/roman-numerals/.approaches/recurse-match/content.md @@ -2,7 +2,7 @@ ```python ARABIC_NUM = (1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1) -ROMAN_NUM = ("M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I") +ROMAN_NUM = ('M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I') def roman(number: int) -> str: return roman_recur(number, 0, []) @@ -19,11 +19,11 @@ def roman_recur(num: int, idx: int, digits: list[str]): [Recursion][recursion] is possible in Python, but it is much less commonly used than in some other languages. -A limitation is the lack of tail-recursion optimization, which can easily trigger stack overflow if the recursion goes too deep. +A major limitation is the lack of tail-call optimization, which can easily trigger stack overflow if the recursion goes too deep. The maximum recursion depth for Python defaults to 1000 to avoid this overflow. However, Roman numerals are so limited in scale that they could be an ideal use case for playing with recursion. -In practice, there is no obvious advantage to recursion over using a loop (_everything you can do with recursion you can do with a loop and vice-versa_) . +In practice, there is no obvious advantage to recursion over using a loop (_everything you can do with recursion you can do with a loop and vice-versa_). Note the use of [structural pattern matching][pep-636], available in Python since version 3.10. There is also an [official tutorial][structural-pattern-matching] for this new feature. @@ -35,23 +35,23 @@ Once we get past the unfamiliar-in-Python syntax, this code is doing essentially Without the pattern matching, a recursive approach might look something like this: ```python -LOOKUP = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"), (100, "C"), (90, "XC"), (50, "L"), - (40, "XL"), (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")] +LOOKUP = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'), (50, 'L'), + (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')] -def convert (number, idx, output): +def convert(number, idx, output): if idx > 12: return output - val, ltr = LOOKUP[idx] + val, roman_val = LOOKUP[idx] if number >= val: - return convert(number - val, idx, output + ltr) + return convert(number - val, idx, output + roman_val) return convert(number, idx + 1, output) def roman(number): - return convert(number, 0, "") + return convert(number, 0, '') ``` [recursion]: https://diveintopython.org/learn/functions/recursion [pep-636]: https://peps.python.org/pep-0636/ [structural-pattern-matching]: https://docs.python.org/3/tutorial/controlflow.html#match-statements -[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-roman +[loop-over-romans]: https://exercism.org/tracks/python/exercises/roman-numerals/approaches/loop-over-romans diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/content.md b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md index e0ea07539f0..b95395f27c3 100644 --- a/exercises/practice/roman-numerals/.approaches/table-lookup/content.md +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/content.md @@ -2,23 +2,21 @@ ```python def roman(number): - assert (number > 0) - # define lookup table (as a tuple of tuples, in this case) table = ( - ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), - ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), - ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), - ("M", "MM", "MMM")) + ('I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'), + ('X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'), + ('C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'), + ('M', 'MM', 'MMM')) # convert the input integer to a list of single digits digits = [int(d) for d in str(number)] - # we need the row in the lookup table for our most-significant decimal digit - inverter = len(digits) - 1 + # get the row in the lookup table for the most-significant decimal digit + inverter = len(digits) - 1 # translate decimal digits list to Roman numerals list - roman_digits = [table[inverter - i][d - 1] for (i, d) in enumerate(digits) if d != 0] + roman_digits = [table[inverter - i][d - 1] for i, d in enumerate(digits) if d != 0] # convert the list of Roman numerals to a single string return ''.join(roman_digits) @@ -31,7 +29,7 @@ Each digit can then be converted to its Roman equivalent with a single lookup. Note that we need to compensate for Python's zero-based indexing by (in effect) subtracting 1 from each row and column. -## Optional modification +## Variation #1 In the code above, we used the `inverter` variable to work bottom-to-top through the lookup table. This allows working left-to-right through the decimal digits. @@ -40,20 +38,18 @@ Alternatively, we could reverse the `digits` list, go top-to-bottom through the ```python def roman(number): - assert (number > 0) - # define lookup table (as a tuple of tuples, in this case) table = ( - ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), - ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), - ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), - ("M", "MM", "MMM")) + ('I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'), + ('X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'), + ('C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'), + ('M', 'MM', 'MMM')) # convert the input integer to a list of single digits, in reverse order digits = [int(d) for d in str(number)][::-1] # translate decimal digits list to Roman numerals list - roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] + roman_digits = [table[i][d - 1] for i, d in enumerate(digits) if d != 0] # reverse the list of Roman numerals and convert to a single string return ''.join(roman_digits[::-1]) diff --git a/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt index 9d69b8c5dae..0ad912bd504 100644 --- a/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt +++ b/exercises/practice/roman-numerals/.approaches/table-lookup/snippet.txt @@ -1,8 +1,8 @@ - table = ( - ("I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"), - ("X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"), - ("C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"), - ("M", "MM", "MMM")) - digits = [int(d) for d in str(number)][::-1] - roman_digits = [table[i][d - 1] for (i, d) in enumerate(digits) if d != 0] - return ''.join(roman_digits[::-1]) +table = ( + ('I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'), + ('X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'), + ('C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM'), + ('M', 'MM', 'MMM')) +digits = [int(d) for d in str(number)][::-1] +roman_digits = [table[i][d - 1] for i, d in enumerate(digits) if d != 0] +return ''.join(roman_digits[::-1])