Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create mentoring.md for Say #2211

Merged
merged 11 commits into from
Feb 16, 2024
127 changes: 127 additions & 0 deletions tracks/python/exercises/say/mentoring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# 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.
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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
```
digits = [""] + "one two three four five six seven eight nine".split()
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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 number < 20 and number >= 10:
roxgib marked this conversation as resolved.
Show resolved Hide resolved
return teens[number % 10]
if number < 100:
if number // 10 and number % 10:
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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:
```
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
def say3(number):
roxgib marked this conversation as resolved.
Show resolved Hide resolved
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 += say3(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.
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
These can be done as either lists or dicts, but most students will fine dicts easier to work with for this problem.
roxgib marked this conversation as resolved.
Show resolved Hide resolved
Encourage the student to define these at the start rather than embedding them in their code.

### Values below 1000
Next, the instructions suggest begining by creating a function which works for values of up to three digits.
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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 are the most straightforward approach.
roxgib marked this conversation as resolved.
Show resolved Hide resolved

Another valid approach is to only handle values below 100, and instead handle the hundreds alongside the other denominators.
This approach 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.
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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.
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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.
This can be handled either with numeric operators or by casting to a string.
Generally a student will find it easier to work with the number as a string for this problem, but both approaches are valid.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there data to support this claim? Does this claim help the mentor? What is the purpose of this sentence?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 85? I would have thought indexing a string would be easier for most students than using numeric operators to isolate a particular digit, but you're right that it's just my intuition, so I'll update that line.


## Talking points
The student should be familiar with comprehensions - if they aren't, introduce the concept as they are very useful here.
roxgib marked this conversation as resolved.
Show resolved Hide resolved

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this list of functions is super helpful when mentoring an exercise. As a mentor, I'd want to see discussions on approaches and solutions at a higher level, not specific string methods which may or may not be useful here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do see your point about the specificity of some lines - 97, 99-101 are probably unnecessary. But since there's a lot going on in this exercise, I figured having a list of suggested techniques would be helpful as a quick reference - if a mentor scans this list before reading the student's code they'll more likely spot that a certain piece of code might make use of something here and be a good opportunity to introduce it to the student. Otherwise many of these wouldn't come to mind without more time spent on the exercise. I think it can be quite helpful to be introduced to a technique through a real example where it improves the code you've already written - getting a sense of when some of these would be useful is harder in contrived examples imo, but for slightly less common constructions it's easy to miss good opportunities to teach 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
* Ternery if-else
roxgib marked this conversation as resolved.
Show resolved Hide resolved
* 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 seperator as well as handle negative or larger numbers.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seperator -> separator

SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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, so don't require the student to write concise code on every iteration.
roxgib marked this conversation as resolved.
Show resolved Hide resolved
A terse solution which demonstrates some techniques:
```
SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code uses both double quotes and single quotes. Either is fine, but avoid mixing and matching! Consistent code is good code.

I'm also not sure what the value of this code block is. Is there something specific this is calling to a mentor's attention?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I've fixed the quotes.

Basically since there's a lot of different techniques that can be applied here I wanted to give a third example to cover some more of them. It isn't necessarily intended to show best practice, but some of the techniques used would be suitable for a student to use. I put it at the end because there's already two examples at the top.


def say(n):
if 0 > n or n > 999999999999:
raise ValueError("input out of range")
if n < 20 and n >= 10:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 <= n < 20

SleeplessByte marked this conversation as resolved.
Show resolved Hide resolved
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 []))
```