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

Added ch12 Dictionaries. #370

Merged
merged 36 commits into from
Oct 16, 2022
Merged

Conversation

AndrewPluzhnikov
Copy link
Contributor

@AndrewPluzhnikov AndrewPluzhnikov commented Jul 12, 2022

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]

@alexmojaki
Copy link
Owner

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.

core/exercises.py Outdated Show resolved Hide resolved
@alexmojaki
Copy link
Owner

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.

Apparently the names of the classes matter somehow - not sure how
though.
We moved the text to where it should appear(after the program_indented)
but it doesn't appear, even after the step is finished. plz help
@alexmojaki
Copy link
Owner

Sorry it took me so long.

No need to apologize, I appreciate the help.

Why the new PR in #383? This one is identical and the remaining comments are still relevant.

@alexmojaki
Copy link
Owner

It's my turn to apologise for slowness 😅

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
Copy link
Owner

@alexmojaki
Copy link
Owner

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 alexmojaki merged commit 58271b7 into alexmojaki:master Oct 16, 2022
@AndrewPluzhnikov
Copy link
Contributor Author

AndrewPluzhnikov commented Nov 7, 2022 via email

@alexmojaki
Copy link
Owner

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'!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants