From c002817f227a1590232f930df2db1ae30d6ea9c5 Mon Sep 17 00:00:00 2001 From: colinleach Date: Thu, 29 Feb 2024 13:30:59 -0700 Subject: [PATCH 1/3] [All Your Base] draft approaches doc --- .../all-your-base/.approaches/config.json | 7 + .../all-your-base/.approaches/introduction.md | 147 ++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 exercises/practice/all-your-base/.approaches/config.json create mode 100644 exercises/practice/all-your-base/.approaches/introduction.md diff --git a/exercises/practice/all-your-base/.approaches/config.json b/exercises/practice/all-your-base/.approaches/config.json new file mode 100644 index 00000000000..21b3a127207 --- /dev/null +++ b/exercises/practice/all-your-base/.approaches/config.json @@ -0,0 +1,7 @@ +{ + "introduction": { + "authors": ["colinleach", + "BethanyG"], + "contributors": [] + } +} diff --git a/exercises/practice/all-your-base/.approaches/introduction.md b/exercises/practice/all-your-base/.approaches/introduction.md new file mode 100644 index 00000000000..373f0523b5b --- /dev/null +++ b/exercises/practice/all-your-base/.approaches/introduction.md @@ -0,0 +1,147 @@ +# Introduction + +The main aim of this exercise is to understand how non-negative integers work in different bases. + +Given that mathematical understanding, the code to implement it can be relatively simple. + +For this exercise, no attempt was made to benchmark performance, as this would distract from the main focus of writing clear, correct code. + +## General guidance + +Essentially all succesful solutions involve three steps: + +1. Check that inputs are valid. +2. Convert the input list to a Python `int`. +3. Convert that `int` to an output list in the new base. + +Some programmers prefer to separate the two conversions into separate functions, others put everything in a single function. + +This is largely a matter of taste, and either structure can be made reasonably concise and readable. + +## 1. Check the inputs + +```python + if input_base < 2: + raise ValueError("input base must be >= 2") + + if not all( 0 <= digit < input_base for digit in digits) : + raise ValueError("all digits must satisfy 0 <= d < input base") + + if not output_base >= 2: + raise ValueError("output base must be >= 2") + +``` + +A valid number base must be `>=2` and all digits must be at least zero and strictly less than the number base. + +For the familiar base-10 system, this means 0 to 9. + +As implemented, the tests require that invalid input raise a `ValueError` with a suitable error message. + +## 2. Convert the input digits to an `int` + +These four code fragments all do essentially the same thing: + +```python +# Simplest loop + val = 0 + for digit in digits: + val = input_base * val + digit + +# Loop, separating the arithmetic steps + val = 0 + for digit in digits: + val *= input_base + val += digit + +# Sum a comprehension over reversed digits + val = sum(digit * input_base ** pos for pos, digit in enumerate(reversed(digits))) + +# Sum a comprehension with alternative reversing + val = sum((digit * (input_base ** (len(digits) - 1 - i)) for i, digit in enumerate(digits))) +``` + +In the first two, the `val *= input_base` step essentially left-shifts all the previous digits, and `val += digit` adds a new digit on the right. + +In the two comprehensions, an exponentation like `input_base ** pos` left-shifts the current digit to the appropriate position in the output. + +*Please think about this until it makes sense:* these short code fragments are the main point of the exercise. + +In each code fragment, the Python `int` is called `val`, a deliberately neutral identifier. + +Surprisingly many students use names like `decimal` or `base10` for the intermediate value, which is misleading. + +A Python `int` is an object with a complicated (but largely hidden) implementation. + +There are methods to convert an `int` to string representations such as decimal, binary or hexadecimal, but the internal representation of `int` is certainly not decimal. + +## 3. Convert the intermediate `int` to output digits + +Now we have to reverse step 2, with a different base. + +```python + out = [] + +# Step forward, insert new digits at beginning + while val > 0: + out.insert(0, val % output_base) + val = val // output_base + +# Insert at end, then reverse + while val: + out.append(val % output_base) + val //= output_base + out.reverse() + +# Use divmod() + while val: + div, mod = divmod(val, output_base) + out.append(mod) + val = div + out.reverse() +``` + +Again, there are multiple code snippets shown above, which all do the same thing. + +In each case, we essentially need the value and remainder of an integer division. + +The first snippet above adds new digits at the start of the list, while the next two add at the end. + +This is a choice of where to take the performance hit: appending to the end is a faster way to grow the list, but needs an extra reverse step. + +The choice of append-reverse would be obvious in Lisp or SML, but the difference is less important in Python. + +```python +# return, with guard for empty list + return out or [0] +``` + +Finally, we return the digits just calculated. + +A minor complcation is that a zero value should be `[0]`, not `[]`. + +Here, we cover this case in the `return` statement, but it could also have been trapped at the beginning of the program, with an early `return`. + +## Recursion option + +```python +def base2dec(input_base: int, digits: list[int]) -> int: + if not digits: + return 0 + return input_base * base2dec(input_base, digits[:-1]) + digits[-1] + + +def dec2base(number: int, output_base: int) -> list[int]: + if not number: + return [] + return [number % output_base] + dec2base(number // output_base, output_base) +``` + +An unusual solution to the two conversions is shown above. + +It works, and the problem is small enough to avoid stack overflow (Python has no tail recursion). + +In practice, few Python programmers would take this approach in a language without the appropriate performance optimizations. + +To simplify: Python only *allows* recursion, it does nothing to *encourage* it: in contrast to Scala, Elixir, and similar languages. + From fceb279deff7ae8bb82770c3fbc01247a1d5b8dd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 11 Apr 2026 12:42:17 -0700 Subject: [PATCH 2/3] Cleaned up the language a bit and added a couple of examples. --- .../all-your-base/.approaches/introduction.md | 155 ++++++++++-------- 1 file changed, 87 insertions(+), 68 deletions(-) diff --git a/exercises/practice/all-your-base/.approaches/introduction.md b/exercises/practice/all-your-base/.approaches/introduction.md index 373f0523b5b..093950bfa0b 100644 --- a/exercises/practice/all-your-base/.approaches/introduction.md +++ b/exercises/practice/all-your-base/.approaches/introduction.md @@ -1,24 +1,28 @@ # Introduction -The main aim of this exercise is to understand how non-negative integers work in different bases. +The main aim of `All Your Base` is to understand how non-negative integers work in different bases. +Given that mathematical understanding, implementation can be relatively straightforward. -Given that mathematical understanding, the code to implement it can be relatively simple. -For this exercise, no attempt was made to benchmark performance, as this would distract from the main focus of writing clear, correct code. +For this approach and its variations, no attempt was made to benchmark performance as this would distract from the main focus of writing clear, correct code for conversion. + ## General guidance -Essentially all succesful solutions involve three steps: +All successful solutions for base conversion involve three steps: -1. Check that inputs are valid. -2. Convert the input list to a Python `int`. -3. Convert that `int` to an output list in the new base. +1. Check that inputs are valid (no non-integer or negative values). +2. Convert the input list to a Python `int`, per the examples given in the instructions. +3. Convert the `int` from step 2 into an output list in the new base. Some programmers prefer to separate the two conversions into separate functions, others put everything in a single function. - This is largely a matter of taste, and either structure can be made reasonably concise and readable. -## 1. Check the inputs + +## 1. Checking the inputs + +Solution code should check that the input base is at least 2, and that the output base is 2 or greater. +Bases outside the range should rase `ValueError`s for input base and output base respectively. ```python if input_base < 2: @@ -32,116 +36,131 @@ This is largely a matter of taste, and either structure can be made reasonably c ``` -A valid number base must be `>=2` and all digits must be at least zero and strictly less than the number base. +Additionally, all input numbers should be positive integers greater or equal to 0 and strictly less than the given number base. +For the familiar base-10 system, that would mean 0 to 9. +As implemented, the tests require that invalid inputs raise a `ValueError` with "all digits must satisfy 0 <= d < input base" as an error message. -For the familiar base-10 system, this means 0 to 9. - -As implemented, the tests require that invalid input raise a `ValueError` with a suitable error message. ## 2. Convert the input digits to an `int` -These four code fragments all do essentially the same thing: +The next step in the conversion process requires that the input list of numbers be converted into a single integer. +The four code fragments below all show variations of this conversion: ```python -# Simplest loop - val = 0 +# Simple loop + value = 0 for digit in digits: - val = input_base * val + digit + value = input_base * value + digit # Loop, separating the arithmetic steps - val = 0 + value = 0 for digit in digits: - val *= input_base - val += digit + value *= input_base + value += digit -# Sum a comprehension over reversed digits - val = sum(digit * input_base ** pos for pos, digit in enumerate(reversed(digits))) +# Sum a generator expression over reversed digits + value = sum(digit * input_base ** position for position, digit in enumerate(reversed(digits))) -# Sum a comprehension with alternative reversing - val = sum((digit * (input_base ** (len(digits) - 1 - i)) for i, digit in enumerate(digits))) +# Sum a generator expression with alternative reversing + value = sum(digit * (input_base ** (len(digits) - 1 - index)) for index, digit in enumerate(digits)) ``` -In the first two, the `val *= input_base` step essentially left-shifts all the previous digits, and `val += digit` adds a new digit on the right. - -In the two comprehensions, an exponentation like `input_base ** pos` left-shifts the current digit to the appropriate position in the output. +In the first two, the `value *= input_base` step essentially left-shifts all the previous digits, and `value += digit` adds a new digit on the right. +In the two generator expressions, an exponentation like `input_base ** position` left-shifts the current digit to the appropriate position in the output. -*Please think about this until it makes sense:* these short code fragments are the main point of the exercise. -In each code fragment, the Python `int` is called `val`, a deliberately neutral identifier. +````exercism/note -Surprisingly many students use names like `decimal` or `base10` for the intermediate value, which is misleading. +It is important to think about these procedures until they makes sense: these short code fragments are the main point of the exercise. +In each code fragment, the Python `int` is called `value`, a deliberately neutral identifier. +Surprisingly many students use names like `decimal` or `base10` for the intermediate value, which is misleading. A Python `int` is an object with a complicated (but largely hidden) implementation. +There are methods to convert an `int` to string representations such as octal, binary or hexadecimal, but these do not change the internal representation. +```` -There are methods to convert an `int` to string representations such as decimal, binary or hexadecimal, but the internal representation of `int` is certainly not decimal. ## 3. Convert the intermediate `int` to output digits -Now we have to reverse step 2, with a different base. +The `int` created in step 2 can now be reversed, using a different base. + +Again, there are multiple code snippets shown below, which all do the same thing (essentially). +In each case, we need the value and the remainder of integer division. +The first snippet adds new digits at the start of the `list`, while the next two add them at the end. +The final snippet uses [`collections.deque()`][deque] to prepend, then converts to a `list` in the `return` statement. + + +These snippets represent choices of where to take the performance hit: appending to the end is a **much** faster and more memory efficient way to grow a `list` (O(1)), but the solution then needs an extra reverse step, incurring O(n) performance for the reversal. +_Prepending_ to the `list` is very expensive, as every addition needs to move all other elements of the list "over" into new memory. +The `deque` has O(1) prepends and appends, but then needs to be converted to a `list` before being returned, which is an O(n) operation. + ```python - out = [] +from collections import deque + -# Step forward, insert new digits at beginning - while val > 0: - out.insert(0, val % output_base) - val = val // output_base +out = [] -# Insert at end, then reverse - while val: - out.append(val % output_base) - val //= output_base +# Step forward, insert new digits at index 0 (front of list). +# Least performant, and not recommended for large amounts of data. + while value > 0: + out.insert(0, value % output_base) + value = value // output_base + +# Append values to the end (mor efficient), then reverse the list. + while value: + out.append(value % output_base) + value //= output_base out.reverse() -# Use divmod() - while val: - div, mod = divmod(val, output_base) +# Use divmod() and reverse list, same efficiency a above. + while value: + div, mod = divmod(value, output_base) out.append(mod) - val = div + value = div out.reverse() -``` + +# Use deque() for effcient appendleft(), convert to list. + converted_digits = deque() -Again, there are multiple code snippets shown above, which all do the same thing. + while number > 0: + converted_digits.appendleft(number % output_base) + number = number // output_base -In each case, we essentially need the value and remainder of an integer division. + return list(converted_digits) or [0] +``` -The first snippet above adds new digits at the start of the list, while the next two add at the end. -This is a choice of where to take the performance hit: appending to the end is a faster way to grow the list, but needs an extra reverse step. +Finally, we return the digits just calculated. + +A minor complication is that a zero value needs to be `[0]`, not `[]` according to the tests. +Here, we cover this case in the `return` statement, but it could also have been trapped at the beginning of the program, with an early `return`: -The choice of append-reverse would be obvious in Lisp or SML, but the difference is less important in Python. ```python # return, with guard for empty list return out or [0] ``` -Finally, we return the digits just calculated. +## Recursion option -A minor complcation is that a zero value should be `[0]`, not `[]`. +An unusual solution to the two conversions is shown below. +It works, and the problem is small enough to avoid stack overflow (Python has no tail recursion). -Here, we cover this case in the `return` statement, but it could also have been trapped at the beginning of the program, with an early `return`. -## Recursion option +In practice, few Python programmers would take this approach without carefully thinking about the bounds of the program and any possible memoization/performance optimizations they could take to avoid issues. +While Python *allows* recursion, it does nothing to *encourage* it, and the default recursion limit is set to only 1000 stack frames. + ```python -def base2dec(input_base: int, digits: list[int]) -> int: +def base_to_dec(input_base, digits): if not digits: return 0 - return input_base * base2dec(input_base, digits[:-1]) + digits[-1] + return input_base * base_to_dec(input_base, digits[:-1]) + digits[-1] -def dec2base(number: int, output_base: int) -> list[int]: +def dec_to_base(number, output_base): if not number: return [] - return [number % output_base] + dec2base(number // output_base, output_base) + return [number % output_base] + dec_to_base(number // output_base, output_base) ``` - -An unusual solution to the two conversions is shown above. - -It works, and the problem is small enough to avoid stack overflow (Python has no tail recursion). - -In practice, few Python programmers would take this approach in a language without the appropriate performance optimizations. - -To simplify: Python only *allows* recursion, it does nothing to *encourage* it: in contrast to Scala, Elixir, and similar languages. - From 2d1f8f6920f5aa5cf1b6d51004fd264d4fe4bb92 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 11 Apr 2026 16:40:58 -0700 Subject: [PATCH 3/3] Added deque ref at end of doc. --- exercises/practice/all-your-base/.approaches/introduction.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exercises/practice/all-your-base/.approaches/introduction.md b/exercises/practice/all-your-base/.approaches/introduction.md index 093950bfa0b..f083c9c08b3 100644 --- a/exercises/practice/all-your-base/.approaches/introduction.md +++ b/exercises/practice/all-your-base/.approaches/introduction.md @@ -164,3 +164,6 @@ def dec_to_base(number, output_base): return [] return [number % output_base] + dec_to_base(number // output_base, output_base) ``` + +[deque]: https://docs.python.org/3/library/collections.html#collections.deque +