futurecoder icon indicating copy to clipboard operation
futurecoder copied to clipboard

Added ch12 Dictionaries.

Open AndrewPluzhnikov opened this issue 3 years ago • 3 comments

Alex,

Here is approximately the first half of the Dictionaries chapter from issue #363 . Please let me know if I am making some mistakes or if you have any comments.

I also fixed a bug in exercises.py. When the input parameter is of type Dict[str, str], running generate.sh resulted in the error below:

Traceback (most recent call last):
  File "C:\Program Files\Python39\lib\runpy.py", line 197, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "C:\Program Files\Python39\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\Andrew\futurecoder\translations\generate_po_file.py", line 163, in <module>
    main()
  File "C:\Users\Andrew\futurecoder\translations\generate_po_file.py", line 47, in main
    for step_name, text in zip(page.step_names, page.step_texts(raw=True)):
  File "C:\Users\Andrew\futurecoder\core\text.py", line 329, in step_texts
    result = [step.raw_text if raw else step.text for step in cls.steps[:-1]] + [cls.final_text]
  File "C:\Users\Andrew\futurecoder\core\text.py", line 372, in steps
    return [self.get_step(step_name) for step_name in self.step_names]
  File "C:\Users\Andrew\futurecoder\core\text.py", line 372, in <listcomp>
    return [self.get_step(step_name) for step_name in self.step_names]
  File "C:\Users\Andrew\futurecoder\core\text.py", line 325, in get_step
    clean_step_class(step)
  File "C:\Users\Andrew\futurecoder\core\text.py", line 143, in clean_step_class
    cls.test_exercise(cls.solution)
  File "C:\Users\Andrew\futurecoder\core\text.py", line 644, in test_exercise
    for inputs, result in cls.test_values():
  File "C:\Users\Andrew\futurecoder\core\text.py", line 637, in test_values
    inputs = cls.generate_inputs()
  File "C:\Users\Andrew\futurecoder\core\text.py", line 653, in generate_inputs
    return {
  File "C:\Users\Andrew\futurecoder\core\text.py", line 654, in <dictcomp>
    name: generate_for_type(typ)
  File "C:\Users\Andrew\futurecoder\core\exercises.py", line 130, in generate_for_type
    return {
KeyError: typing.Dict[str, str]

AndrewPluzhnikov avatar Jul 12 '22 02:07 AndrewPluzhnikov

Thank you! This is a good start. Almost all these comments are about things that are very easy to fix.

I also fixed a bug in exercises.py. When the input parameter is of type Dict[str, str], running generate.sh resulted in the error below:

That's because that type of parameter has never appeared in exercises before! By the way, when you run generate.sh, the files it changes are meant to be committed and pushed.

alexmojaki avatar Jul 16 '22 14:07 alexmojaki

Looks like you're having git problems. I completely sympathise, I've had similar struggles and I still don't fully understand this stuff. The easiest option might be to just create a new branch from master, copy in the files that you're actually changing (I think there's just two), and make a new PR.

alexmojaki avatar Aug 07 '22 11:08 alexmojaki

Sorry it took me so long.

No need to apologize, I appreciate the help.

Why the new PR in https://github.com/alexmojaki/futurecoder/pull/383? This one is identical and the remaining comments are still relevant.

alexmojaki avatar Sep 14 '22 16:09 alexmojaki

It's my turn to apologise for slowness :sweat_smile:

Let me know what you think of my changes. Otherwise there's a TODO that needs resolving, and I think the hints need to be fleshed out more.

alexmojaki avatar Oct 08 '22 11:10 alexmojaki

Preview: https://futurecoder-ta--dicts-ai9rq6np.web.app/course/#toc

alexmojaki avatar Oct 15 '22 19:10 alexmojaki

Thank you for your help!

Do you want to continue with the rest of the dicts chapter? Alternatively, I saw you started translating to Russian way back. I don't know why you chose to stop, but that would still be appreciated!

alexmojaki avatar Oct 16 '22 09:10 alexmojaki

Hi Alex,

I was wondering if you had any other draft chapters written up in markup which I could try to work on?

Sincerely, Andrew Pluzhnikov

On Sun, Oct 16, 2022 at 2:26 AM Alex Hall @.***> wrote:

Merged #370 https://github.com/alexmojaki/futurecoder/pull/370 into master.

— Reply to this email directly, view it on GitHub https://github.com/alexmojaki/futurecoder/pull/370#event-7596117237, or unsubscribe https://github.com/notifications/unsubscribe-auth/AS3GQAALIVTBLRX6KIDB5JTWDPC33ANCNFSM53JO7N2A . You are receiving this because you authored the thread.Message ID: @.***>

AndrewPluzhnikov avatar Nov 07 '22 00:11 AndrewPluzhnikov

Hi @AndrewPluzhnikov, I appreciate the enthusiasm to keep contributing. I've just finished another page on https://demo.hedgedoc.org/OH78A7uwTDOjeMM4v0QFLg?edit, here's a copy:

Now we'll learn how to add key-value pairs to a dictionary, e.g. so that we can keep track of what the customer is buying. Before looking at dictionaries, let's remind ourselves how to add items to a list. Run this program:

__copyable__
cart = []
cart.append('dog')
cart.append('box')
print(cart)

Pretty simple. We can also change the value at an index, replacing it with a different one:

__copyable__
cart = ['dog', 'cat']
cart[1] = 'box'
print(cart)

// This step should have predicted_output_choices, with ['dog', 'box'] and ['box', 'cat'] as options.


What if we used that idea to create our list in the first place? We know we want a list where cart[0] is 'dog' and cart[1] is 'box', so let's just say that:

__copyable__
cart = []
cart[0] = 'dog'
cart[1] = 'box'
print(cart)

Sorry, that's not allowed. For lists, subscript assignment only works for existing valid indices. But that's not true for dictionaries! Try this:

quantities = {}
quantities['dog'] = 5000000
quantities['box'] = 2
print(quantities)

Note that {} means an empty dictionary, i.e. a dictionary with no key-value pairs. This is similar to [] meaning an empty list or "" meaning an empty string.

// This step should have predicted_output_choices


That's exactly what we need. When the customer says they want 5 million boxes, we can just put that information directly into our dictionary. So as an exercise, let's make a generic version of that. Write a function buy_quantity(quantities) which calls input() twice to get an item name and quantity from the user and assigns them in the quantities dictionary. Here's some starting code:

__copyable__
def buy_quantity(quantities):
    print('Item:')
    item = input()
    print('Quantity:')
    ...

def test():
    quantities = {}
    buy_quantity(quantities)
    print(quantities)
    buy_quantity(quantities)
    print(quantities)

test()

and an example of how a session should look:

Item:
dog
Quantity:
5000000
{'dog': 5000000}
Item:
box
Quantity:
2
{'dog': 5000000, 'box': 2}

Note that buy_quantity should modify the dictionary that's passed in, and doesn't need to return anything. You can assume that the user will enter a valid integer for the quantity.

// This also needs a MessageStep which is triggered if the user's solution is correct except that the values // are still strings instead of ints.


Well done!

Next exercise: earlier we defined a function total_cost(quantities, prices) which returned a single number with a grand total of all the items in the cart. Now let's make a function total_cost_per_item(quantities, prices) which returns a new dictionary with the total cost for each item:

__copyable__
def total_cost_per_item(quantities, prices):
    totals = {}
    for item in quantities:
        ... = quantities[item] * prices[item]
    return totals

assert_equal(
    total_cost_per_item({'apple': 2}, {'apple': 3, 'box': 5}),
    {'apple': 6},
)

assert_equal(
    total_cost_per_item({'dog': 5000000, 'box': 2}, {'dog': 100, 'box': 5}),
    {'dog': 500000000, 'box': 10},
)

Perfect! This is like having a nice receipt full of useful information.

Let's come back to the example of using dictionaries for translation. Suppose we have one dictionary for translating from English to French, and another for translating from French to German. Let's use that to create a dictionary that translates from English to German:

__copyable__
def make_english_to_german(english_to_french, french_to_german):
    ...
    // The user has to write the whole function, here's an example solution:
    english_to_german = {}
    for english in english_to_french:
        french = english_to_french[english]
        german = french_to_german[french]
        english_to_german[english] = german
    return english_to_german

assert_equal(
    make_english_to_german(
        {'apple': 'pomme', 'box': 'boite'},
        {'pomme': 'apfel', 'boite': 'kasten'},
    ),
    {'apple': 'apfel', 'box': 'kasten'},
)

// this step should have parsons_solution = True


Great job!

Of course, language isn't so simple, and there are many ways that using a dictionary like this could go wrong. So...let's do something even worse! Write a function which takes a dictionary and swaps the keys and values, so a: b becomes b: a.

__copyable__
def swap_keys_values(d):
    ...

assert_equal(
    swap({'apple': 'pomme', 'box': 'boite'}),
    {'pomme': 'apple', 'boite': 'box'},
)

Magnificent!

Jokes aside, it's important to remember how exactly this can go wrong. Just like multiple items in the store can have the same price, multiple words in English can have the same translation in French.

But there are many situations where you can be sure that the values in a dictionary are unique and that this 'inversion' makes sense. For example, we saw this code earlier in the chapter:

__copyable__
__no_auto_translate__
def substitute(string, d):
    result = ""
    for letter in string:
        result += d[letter]
    return result

plaintext = 'helloworld'
encrypted = 'qpeefifmez'
letters = {'h': 'q', 'e': 'p', 'l': 'e', 'o': 'f', 'w': 'i', 'r': 'm', 'd': 'z'}
reverse = {'q': 'h', 'p': 'e', 'e': 'l', 'f': 'o', 'i': 'w', 'm': 'r', 'z': 'd'}
assert_equal(substitute(plaintext, letters), encrypted)
assert_equal(substitute(encrypted, reverse), plaintext)

Now we can construct the reverse dictionary automatically:

reverse = swap_keys_values(letters)

For this to work, we just have to make sure that all the values in letters are unique. Otherwise it would be impossible to decrypt messages properly. If both 'h' and 'j' got replaced with 'q' during encryption, there would be no way to know whether 'qpeef' means 'hello' or 'jello'!

alexmojaki avatar Nov 11 '22 10:11 alexmojaki