作业5: 更多字典练习


本周的课堂练习将继续加深你对字典的掌握!

🍞 食谱与库存管理

受疫情期间刷剧和做饭的启发,Parth 和 Peter 决定开一家面包店。为了节省成本,他们希望尽可能自动化库存管理。多亏了106A课程,他们觉得可以用字典来帮忙。

所有可用的原料都存储在pantry字典中,键为原料名,值为重量(当然他们使用公制单位),例如:

pantry = {
    'flour': 400,
    'sugar': 300,
    'salt': 10,
    'chocolate': 150
}

每个食谱也用字典存储,例如以下这个不太令人兴奋的配方:

recipe = {
    'flour': 200,
    'salt': 2.5
}

(如你所见,从上面的食谱来看,他们的面包店可能不会很成功,但现在这不重要。)

食谱和库存最初都存储在文件中,每行一个键值对,用:: 分隔。上面的库存和食谱可能来自如下文本文件:

flour:: 400
sugar:: 300
salt:: 10
chocolate:: 150
flour:: 200
salt:: 2.5

我们的目标是在recipes.py中编写一些函数,帮助Parth和Peter管理面包店。首先实现如下函数,该函数接收包含食谱或库存列表的文件名,并将其读取为字典:

def read_dict_from_file(filename):
    """
    接收包含食谱或库存列表的文件名,并将其读取为字典。
    
    使用上述文件的示例:
    >>> read_dict_from_file('recipe.txt')
    {'flour': 200.0, 'salt': 2.5}
    """

注意:所有重量都已转换为float类型。


🧑‍🍳 判断与制作食谱

一旦你建立了构建食谱和库存列表字典的基础设施,请实现以下函数:

def can_make(recipe, pantry):
    """
    给定库存内容,返回一个布尔值,指示是否可能按照食谱制作。
    注意,此函数的参数是字典,而不是文件名。在此函数中不应修改pantry。
    
    >>> can_make({'flour': 5.0, 'salt': 1.0}, {'flour': 200.0, 'salt': 2.5})
    True
    >>> can_make({'flour': 5.0, 'salt': 5.0}, {'flour': 200.0, 'salt': 2.5})
    False
    """
    pass


def make_recipe(recipe, pantry):
    """
    给定一个食谱和一个有足够原料制作该食谱的库存,修改库存内容以移除食谱所需的原料数量。
    你可以原地修改pantry,但需要返回修改后的pantry以便使用doctest测试输出。
    
    >>> make_recipe({'flour': 5.0, 'salt': 1.0}, {'flour': 200.0, 'salt': 2.5})
    {'flour': 195.0, 'salt': 1.5}
    """
    pass

我们已经为你实现了主函数的一部分,它首先从用户读取库存文件,然后连续询问用户食谱文件名,如果库存没有足够的原料则打印错误消息,或者如果有足够的每种原料则制作食谱(从库存字典中移除原料),并在之后打印库存。

下面是一个示例运行,反复尝试制作上面的食谱,徒劳地希望有人真的想吃它。注意,你的程序应该能够接受任何有效的食谱或库存文件名。

python3 recipes.py
What pantry file would you like to use? pantry.txt
What recipe would you like to make? recipe.txt
Pantry before:
{'flour': 400.0, 'sugar': 300.0, 'salt': 10.0, 'chocolate': 150.0}
Pantry after:
{'flour': 200.0, 'sugar': 300.0, 'salt': 7.5, 'chocolate': 150.0}
What recipe would you like to make next? recipe.txt
Pantry before:
{'flour': 200.0, 'sugar': 300.0, 'salt': 7.5, 'chocolate': 150.0}
Pantry after:
{'flour': 0.0, 'sugar': 300.0, 'salt': 5.0, 'chocolate': 150.0}
What recipe would you like to make next? recipe.txt
You do not have the ingredients for that recipe!

你可以通过在主函数中输入python3 recipes.py来运行这个程序(在Windows计算机上使用python或py)。你可以选择上面的pantry.txt,或者稍大一点的full_pantry.txt。我们还包含了几个食谱文件:recipe.txt、pizza_recipe.txt和cookies_recipe.txt。你也可以发挥创意,制作自己的.txt文件!


🎁 Bonus:f-string美化输出

你会注意到当前打印库存项目的方式不太美观。你的最后一个任务是重新格式化库存项目的打印方式。我们想要的示例运行如下:

python3 recipes.py
What pantry file would you like to use? pantry.txt
What recipe would you like to make? recipe.txt
Pantry before:
flour: 400.0 grams 
sugar: 300.0 grams
salt: 10.0 grams
chocolate: 150.0 grams
Pantry after:
flour: 200.0 grams
sugar: 300.0 grams
salt: 7.5 grams
chocolate: 150.0 grams
What recipe would you like to make next? recipe.txt
Pantry before:
flour: 200.0 grams
sugar: 300.0 grams
salt: 7.5 grams
chocolate: 150.0 grams
Pantry after:
flour: 0.0 grams
sugar: 300.0 grams
salt: 5.0 grams
chocolate: 150.0 grams
What recipe would you like to make next? recipe.txt
You do not have the ingredients for that recipe!

f-string是Python中引入的一个特性,允许简单的字符串格式化。要使用f-string,只需在字符串前加上'f',并将你想要包含的表达式或变量用花括号{}括起来。这些表达式会被求值,它们的值会被插入到字符串中。例如,给定变量name = "Ethan"和age = 21,f-string f"My name is {name}, and I am {age} years old."将输出"My name is Ethan, and I am 21 years old."。f-string提供了一种简洁且可读的方式来在Python中创建动态字符串,提高了代码的可读性和可维护性。


🔐 密码学(加密与解密)

密码学是研究秘密通信技术的学科。想象一下,Alice和Bob想要互相发送消息,但Eve可以窥探他们发送的消息并读取它们。Alice想要找到一种方法来"加密"她的消息,这样如果Eve读取了消息,她就无法理解它,但Bob将能够"解密"消息。我们要编写一个程序来帮助Alice和Bob做到这一点。

Alice决定将她原始消息中的每个字母替换为另一个字母。为了做到这一点,Alice向ChatGPT(一个能够执行各种任务包括生成JSON字符串的强大语言模型)发出了请求。她请求ChatGPT生成一个表示加密字典的JSON字符串。ChatGPT返回了JSON字符串,Alice将其存储在名为ENCRYPTION_JSON_STRING的变量中:

ENCRYPTION_JSON_STRING = '''
{
    "A": "T",
    "B": "H",
    "C": "E",
    "D": "Q",
    "E": "U",
    "F": "I",
    "G": "C",
    "H": "K",
    ...
}
'''

但是,Alice不知道如何将JSON字符串加载到字典中。你需要帮助她完成这部分。为了开始,帮助Alice将JSON字符串ENCRYPTION_JSON_STRING加载到变量ENCRYPTION_DICT中。

Alice和Bob交换了这个ENCRYPTION_DICT字典,所以他们都知道这是策略,但Eve不知道!注意,为了避免歧义,这个字典中的值是唯一的(也就是说,每个字母在字典中恰好作为键和值出现一次)。

请在cryptography.py中实现以下函数:

加密
def encrypt(plaintext):
    """
    接收明文作为输入并返回'密文':用ENCRYPTION_DICT中对应的加密字符替换明文中的每个字母的结果。
    
    明文完全由大写字母和非字母字符(如标点符号)组成。非字母字符不需要加密,但应该以原始形式出现在明文中。
    
    >>> encrypt("HEY, HOW'S IT GOING?")
    "KUD, KXZ'S BV CXBFC?"
    >>> encrypt("I LOVE CS 106A!")
    'B WXLU ES 106T!'
    >>> encrypt("UNICORNS ARE THE MOST BEAUTIFUL ANIMALS IN EXISTENCE")
    'AFBEXPFS TPU VKU NXSV HUTAVBIAW TFBNTWS BF UYBSVUFEU'
    pass
    """
解密
def decrypt(ciphertext):
    """
    使用ENCRYPTION_DICT解密密文的每个字母字符。
    
    >>> decrypt("KUD, KXZ'S BV CXBFC?")
    "HEY, HOW'S IT GOING?"
    >>> decrypt('B WXLU ES 106T!')
    'I LOVE CS 106A!'
    >>> decrypt('AFBEXPFS TPU VKU NXSV HUTAVBIAW TFBNTWS BF UYBSVUFEU')
    'UNICORNS ARE THE MOST BEAUTIFUL ANIMALS IN EXISTENCE'
    pass
    """

补充:这种加密方式(称为替换密码)其实并不安全。你能发现它的哪些问题?Eve可能如何“破译”密文?