Skip to content

Commit

Permalink
Create mentoring.md for Say (#2211)
Browse files Browse the repository at this point in the history
* Create mentoring.md for Say

* Minor changes based on review

* Spelling error (Ternery -> Ternary)

* dict -> dictionary

Co-authored-by: Isaac Good <[email protected]>

* is -> are

Co-authored-by: Isaac Good <[email protected]>

* Update tracks/python/exercises/say/mentoring.md

Co-authored-by: Isaac Good <[email protected]>

* Update tracks/python/exercises/say/mentoring.md

Co-authored-by: Isaac Good <[email protected]>

* Consistent use of quotes

* Update to Parsing the number section

* Apply suggestions from code review

* Update tracks/python/exercises/say/mentoring.md

---------

Co-authored-by: Isaac Good <[email protected]>
Co-authored-by: Derk-Jan Karrenbeld <[email protected]>
  • Loading branch information
3 people authored Feb 16, 2024
1 parent 8b445fb commit 329d882
Showing 1 changed file with 138 additions and 0 deletions.
138 changes: 138 additions & 0 deletions tracks/python/exercises/say/mentoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Mentoring

## Problems and Challenges

This problem requires the student to transform an integer (0 <= n < 1,000,000,000,000) into its full English equivalent, and raise an exception for values outside that range.
Take note of the tests - the use of 'and' as a separator is not required, and adding it will cause the tests to fail, but it is suggested as an extension exercise.

## Reasonable Solution

```python
digits = [""] + "one two three four five six seven eight nine".split()
teens = "ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen".split()
tens = ["", ""] + "twenty thirty forty fifty sixty seventy eighty ninety".split()
denominations = (9, "billion"), (6, "million"), (3, "thousand"), (2, "hundred")

def say(number):
if 0 > number or number > 999999999999:
raise ValueError("input out of range")
if 10 <= number < 20:
return teens[number % 10]
if number < 100:
if number // 10 and number % 10:
return "-".join([tens[number // 10], digits[number % 10]])
else:
return tens[number // 10] or digits[number % 10] or "zero"
for k, denomination in denominations:
if number > 10**k - 1:
result = [say(number // 10**k), denomination]
if number % 10 ** k:
result.append(say(number % 10**k))
return " ".join(result)
```
Or without using recursion:

```python
denominations = (9, "billion"), (6, "million"), (3, "thousand"), (0, "")

def say_three_digits(number):
hundreds_col, tens_col, ones_col = number // 100, number // 10 % 10, number % 10
result = [str(digits[hundreds_col]) + " hundred"] if hundreds_col else []
if tens_col == 1:
result.append(teens[ones_col])
elif tens_col and ones_col:
result.append("-".join([tens[tens_col], digits[ones_col]]))
else:
result.append(tens[tens_col] or digits[ones_col])
return result

def say(number):
if 0 > number or number > 999999999999:
raise ValueError("input out of range")
result = []
for k, denomination in denominations:
if number > 10**k - 1:
result += say_three_digits(number // 10**k)
result += [denomination]
number %= 10**k
return " ".join(e for e in result if e) or "zero"
```

## Common Suggestions

### Defining the literals

Typically this problem will begin with defining the literals needed.
These can be done as either lists or dictionaries; many students will find dictionaries easier to work with for this problem.
Encourage the student to define these at the start rather than embedding them in their code.

### Values below 1000

Next, the instructions suggest beginning by creating a function which works for values of up to three digits.
The return value of this function will typically be a string, but it can also be a list of strings for later assembly.
Whether in this function or for the final return, encourage them to use a list and combine the elements using `str.join`.

The main challenges are ensuring that the 'hundred' denominator is conditionally included and that the tens and ones columns are joined with a hyphen if they are both present.
Simple `if` checks for each of these conditions is the most straightforward approach.

Another approach is to only handle values below 100, and instead handle the hundreds alongside the other denominators.
This typically requires the use of recursion.

### Larger numbers

Encourage the student to use the first function they wrote for each set of three digits rather than duplicating code.
Each set of three can be checked individually and conditionally added along with the appropriate denominator, or alternatively the denominators can be stored in a list and iterated over.

### Zero or invalid input

Finally, the student must add a guard that returns a `ValueError` if the input is out of range.
A guard is also the easiest way to handle the zero case, but this can also be handled through short circuit evaluation.

### Parsing the number

The number needs to be broken up into groups of three as well as isolating each digit.
Two common approaches are to use the `//` and `%` operators or to cast the integer to a string and access the digits by indexing or iterating.

## Talking Points

List comprehensions are very useful for this exercise.

There are a wide variety of valid approaches to this problem, which gives students a lot of opportunity to experiment and encounter new ways of manipulating data.
Some useful techniques that students might not have encountered are listed below.
Students will not need or want to use all of these, but mentors should look for opportunities in the student's code to introduce one or more of them.
* Casting between numeric types and strings
* String indexing, iteration, formatting, or concatenation
* String methods such as `zfill`, `isnumeric`, `split`, or `join`
* List concatenation using the `+` operator
* List methods such as `append` or `extend`
* Iterator functions such as `zip`, `enumerate`, or `filter`
* Assignment operators
* Unpacking operator
* Short circuit evaluation and the use of `or`/`and` for control flow
* Ternary conditional operator
* Integer division and modulo operators, and the divmod function
* Recusion

Students who wish to extend the exercise can rewrite the function to include 'and' as a separator as well as handle negative or larger numbers.
This will require rewriting the tests, which might also serve as a learning opportunity for the student.

This problem is a good opportunity to practice writing concise code and balancing readability.
It is also a good chance to code verbosely while the problem is solved, and then iterate on the solution to reduce length and complexity.
A terse solution which demonstrates some techniques:

```python
digits = [""] + "one two three four five six seven eight nine".split()
teens = "ten eleven twelve thir four fif six seven eigh nine".split()
tens = ["", ""] + "twenty thirty forty fifty sixty seventy eighty ninety".split()
denominations = (9, "billion"), (6, "million"), (3, "thousand"), (2, "hundred")

def say(n):
if 0 > n or n > 999999999999:
raise ValueError("input out of range")
if 10 <= n < 20:
return teens[n % 10] + "teen" if n % 10 > 2 else ""
if n < 100:
return "-".join(filter(bool, [tens[n // 10], digits[n % 10]])) or "zero"
k, d = [t for t in denominations if n > 10**t[0] - 1][0]
return " ".join([say(n // 10**k), d] + ([say(n % 10**k)] if n % 10**k else []))
```

0 comments on commit 329d882

Please sign in to comment.