Skip to content

Commit 7342138

Browse files
committed
CopyingDictionaries
1 parent 391edca commit 7342138

File tree

4 files changed

+1342
-2
lines changed

4 files changed

+1342
-2
lines changed

core/chapters/c12_dictionaries.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,3 +1048,244 @@ def substitute(string, d):
10481048
Otherwise it would be impossible to decrypt messages properly. If both `'h'` and `'j'` got replaced with `'q'`
10491049
during encryption, there would be no way to know whether `'qpeef'` means `'hello'` or `'jello'`!
10501050
"""
1051+
1052+
1053+
class CopyingDictionaries(Page):
1054+
title = "Copying Dictionaries"
1055+
1056+
class shared_references(VerbatimStep):
1057+
"""
1058+
Remember how assigning one list variable to another (`list2 = list1`) made both names point to the *same* list? Dictionaries work the same way because they are also *mutable* (can be changed).
1059+
1060+
Predict what the following code will print, then run it to see:
1061+
1062+
__copyable__
1063+
__program_indented__
1064+
"""
1065+
def program(self):
1066+
d1 = {'a': 1, 'b': 2}
1067+
d2 = d1
1068+
1069+
print("d1 before:", d1)
1070+
print("d2 before:", d2)
1071+
print("Are they the same object?", d1 is d2)
1072+
1073+
d2['c'] = 3 # Modify via d2
1074+
1075+
print("d1 after:", d1) # Is d1 affected?
1076+
print("d2 after:", d2)
1077+
1078+
predicted_output_choices = [
1079+
# Incorrect prediction (d1 unaffected)
1080+
"""d1 before: {'a': 1, 'b': 2}
1081+
d2 before: {'a': 1, 'b': 2}
1082+
Are they the same object? True
1083+
d1 after: {'a': 1, 'b': 2}
1084+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1085+
1086+
# Correct prediction
1087+
"""d1 before: {'a': 1, 'b': 2}
1088+
d2 before: {'a': 1, 'b': 2}
1089+
Are they the same object? True
1090+
d1 after: {'a': 1, 'b': 2, 'c': 3}
1091+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1092+
1093+
# Incorrect prediction (is False)
1094+
"""d1 before: {'a': 1, 'b': 2}
1095+
d2 before: {'a': 1, 'b': 2}
1096+
Are they the same object? False
1097+
d1 after: {'a': 1, 'b': 2}
1098+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1099+
]
1100+
1101+
class making_copies(VerbatimStep):
1102+
"""
1103+
Because `d1` and `d2` referred to the exact same dictionary object (`d1 is d2` was `True`), changing it via `d2` also changed what `d1` saw.
1104+
1105+
To get a *separate* dictionary with the same contents, use the `.copy()` method.
1106+
1107+
Predict how using `.copy()` changes the outcome, then run this code:
1108+
1109+
__copyable__
1110+
__program_indented__
1111+
"""
1112+
def program(self):
1113+
d1 = {'a': 1, 'b': 2}
1114+
d2 = d1.copy() # Create a separate copy
1115+
1116+
print("d1 before:", d1)
1117+
print("d2 before:", d2)
1118+
print("Are they the same object?", d1 is d2)
1119+
1120+
d2['c'] = 3 # Modify the copy
1121+
1122+
print("d1 after:", d1) # Is d1 affected now?
1123+
print("d2 after:", d2)
1124+
1125+
predicted_output_choices = [
1126+
# Incorrect prediction (is True)
1127+
"""d1 before: {'a': 1, 'b': 2}
1128+
d2 before: {'a': 1, 'b': 2}
1129+
Are they the same object? True
1130+
d1 after: {'a': 1, 'b': 2, 'c': 3}
1131+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1132+
1133+
# Incorrect prediction (d1 affected)
1134+
"""d1 before: {'a': 1, 'b': 2}
1135+
d2 before: {'a': 1, 'b': 2}
1136+
Are they the same object? False
1137+
d1 after: {'a': 1, 'b': 2, 'c': 3}
1138+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1139+
1140+
# Correct prediction
1141+
"""d1 before: {'a': 1, 'b': 2}
1142+
d2 before: {'a': 1, 'b': 2}
1143+
Are they the same object? False
1144+
d1 after: {'a': 1, 'b': 2}
1145+
d2 after: {'a': 1, 'b': 2, 'c': 3}""",
1146+
]
1147+
1148+
class positive_stock_exercise(ExerciseStep):
1149+
"""
1150+
Making an exact copy is useful, but often we want a *modified* copy. Let's practice creating a new dictionary based on an old one.
1151+
1152+
Write a function `positive_stock(stock)` that takes a dictionary `stock` (mapping item names to integer quantities) and returns a *new* dictionary containing only the items from the original `stock` where the quantity is strictly greater than 0. The original `stock` dictionary should not be changed.
1153+
1154+
__copyable__
1155+
def positive_stock(stock):
1156+
# Your code here
1157+
...
1158+
1159+
assert_equal(
1160+
positive_stock({'apple': 10, 'banana': 0, 'pear': 5, 'orange': 0}),
1161+
{'apple': 10, 'pear': 5}
1162+
)
1163+
assert_equal(
1164+
positive_stock({'pen': 0, 'pencil': 0}),
1165+
{}
1166+
)
1167+
assert_equal(
1168+
positive_stock({'book': 1, 'paper': 5}),
1169+
{'book': 1, 'paper': 5}
1170+
)
1171+
"""
1172+
hints = """
1173+
Start by creating a new empty dictionary, e.g., `result = {}`.
1174+
Loop through the keys of the input `stock` dictionary.
1175+
Inside the loop, get the `quantity` for the current `item` using `stock[item]`.
1176+
Use an `if` statement to check if `quantity > 0`.
1177+
If the quantity is positive, add the `item` and its `quantity` to your `result` dictionary using `result[item] = quantity`.
1178+
After the loop finishes, return the `result` dictionary.
1179+
Make sure you don't modify the original `stock` dictionary passed into the function. Creating a new `result` dictionary ensures this.
1180+
"""
1181+
1182+
def solution(self):
1183+
def positive_stock(stock: Dict[str, int]):
1184+
result = {}
1185+
for item in stock:
1186+
quantity = stock[item]
1187+
if quantity > 0:
1188+
result[item] = quantity
1189+
return result
1190+
return positive_stock
1191+
1192+
tests = [
1193+
(({'apple': 10, 'banana': 0, 'pear': 5, 'orange': 0},), {'apple': 10, 'pear': 5}),
1194+
(({'pen': 0, 'pencil': 0},), {}),
1195+
(({'book': 1, 'paper': 5},), {'book': 1, 'paper': 5}),
1196+
(({},), {}), # Empty input
1197+
(({'gadget': -5, 'widget': 3},), {'widget': 3}), # Negative values
1198+
]
1199+
1200+
@classmethod
1201+
def generate_inputs(cls):
1202+
# Generate a dictionary with some zero/negative and positive values
1203+
stock = {}
1204+
num_items = random.randint(3, 8)
1205+
for _ in range(num_items):
1206+
item = generate_string(random.randint(3, 6))
1207+
# Ensure some variety in quantities
1208+
if random.random() < 0.4:
1209+
quantity = 0
1210+
elif random.random() < 0.2:
1211+
quantity = random.randint(-5, -1)
1212+
else:
1213+
quantity = random.randint(1, 20)
1214+
stock[item] = quantity
1215+
# Ensure at least one positive if dict not empty
1216+
if stock and all(q <= 0 for q in stock.values()):
1217+
stock[generate_string(4)] = random.randint(1, 10)
1218+
return {"stock": stock}
1219+
1220+
class add_item_exercise(ExerciseStep):
1221+
"""
1222+
Let's practice combining copying and modifying. Imagine we want to represent adding one unit of an item to our stock count.
1223+
1224+
Write a function `add_item(item, quantities)` that takes an item name (`item`) and a dictionary `quantities`. You can assume the `item` *already exists* as a key in the `quantities` dictionary.
1225+
1226+
The function should return a *new* dictionary which is a copy of `quantities`, but with the value associated with `item` increased by 1. The original `quantities` dictionary should not be changed.
1227+
1228+
__copyable__
1229+
def add_item(item, quantities):
1230+
# Your code here
1231+
...
1232+
1233+
stock = {'apple': 5, 'banana': 2}
1234+
new_stock = add_item('apple', stock)
1235+
assert_equal(stock, {'apple': 5, 'banana': 2}) # Original unchanged
1236+
assert_equal(new_stock, {'apple': 6, 'banana': 2}) # Copy has incremented value
1237+
1238+
new_stock_2 = add_item('banana', new_stock)
1239+
assert_equal(new_stock, {'apple': 6, 'banana': 2}) # Previous copy unchanged
1240+
assert_equal(new_stock_2, {'apple': 6, 'banana': 3}) # New copy incremented
1241+
"""
1242+
hints = """
1243+
First, create a *copy* of the input `quantities` dictionary using the `.copy()` method. Store this in a new variable, e.g., `new_quantities`.
1244+
Since we assume `item` is already a key, you don't need to check for its existence in this exercise.
1245+
Find the current quantity of the `item` in the `new_quantities` copy using `new_quantities[item]`.
1246+
Calculate the new quantity by adding 1 to the current quantity.
1247+
Update the value for `item` in the `new_quantities` copy with this new quantity using assignment: `new_quantities[item] = ...`.
1248+
Return the `new_quantities` dictionary.
1249+
"""
1250+
1251+
def solution(self):
1252+
def add_item(item: str, quantities: Dict[str, int]):
1253+
new_quantities = quantities.copy()
1254+
new_quantities[item] = new_quantities[item] + 1
1255+
return new_quantities
1256+
return add_item
1257+
1258+
tests = [
1259+
(('apple', {'apple': 5, 'banana': 2}), {'apple': 6, 'banana': 2}),
1260+
(('banana', {'apple': 6, 'banana': 2}), {'apple': 6, 'banana': 3}),
1261+
(('pen', {'pen': 1}), {'pen': 2}),
1262+
(('a', {'a': 0, 'b': 99}), {'a': 1, 'b': 99}),
1263+
]
1264+
1265+
@classmethod
1266+
def generate_inputs(cls):
1267+
quantities = generate_dict(str, int)
1268+
# Ensure the dictionary is not empty
1269+
if not quantities:
1270+
quantities[generate_string(4)] = random.randint(0, 10)
1271+
# Pick an existing item to increment
1272+
item = random.choice(list(quantities.keys()))
1273+
return {"item": item, "quantities": quantities}
1274+
1275+
final_text = """
1276+
Well done! Notice that the line where you increment the value:
1277+
1278+
new_quantities[item] = new_quantities[item] + 1
1279+
1280+
can also be written more concisely using the `+=` operator, just like with numbers:
1281+
1282+
new_quantities[item] += 1
1283+
1284+
This does the same thing: it reads the current value, adds 1, and assigns the result back.
1285+
"""
1286+
1287+
final_text = """
1288+
Great! You now know why copying dictionaries is important (because they are mutable) and how to do it using `.copy()`. You've also practiced creating modified copies, which is a common and safe way to work with data without accidentally changing things elsewhere in your program.
1289+
1290+
Next, we'll see how to check if a key exists *before* trying to use it, to avoid errors.
1291+
"""

tests/golden_files/en/test_transcript.json

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7437,5 +7437,110 @@
74377437
"result": []
74387438
},
74397439
"step": "swap_keys_values_exercise"
7440+
},
7441+
{
7442+
"get_solution": "program",
7443+
"page": "Copying Dictionaries",
7444+
"program": [
7445+
"d1 = {'a': 1, 'b': 2}",
7446+
"d2 = d1",
7447+
"",
7448+
"print(\"d1 before:\", d1)",
7449+
"print(\"d2 before:\", d2)",
7450+
"print(\"Are they the same object?\", d1 is d2)",
7451+
"",
7452+
"d2['c'] = 3 # Modify via d2",
7453+
"",
7454+
"print(\"d1 after:\", d1) # Is d1 affected?",
7455+
"print(\"d2 after:\", d2)"
7456+
],
7457+
"response": {
7458+
"passed": true,
7459+
"prediction": {
7460+
"answer": "d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? True\nd1 after: {'a': 1, 'b': 2, 'c': 3}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7461+
"choices": [
7462+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? True\nd1 after: {'a': 1, 'b': 2}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7463+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? True\nd1 after: {'a': 1, 'b': 2, 'c': 3}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7464+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? False\nd1 after: {'a': 1, 'b': 2}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7465+
"Error"
7466+
]
7467+
},
7468+
"result": [
7469+
{
7470+
"text": "d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? True\nd1 after: {'a': 1, 'b': 2, 'c': 3}\nd2 after: {'a': 1, 'b': 2, 'c': 3}\n",
7471+
"type": "stdout"
7472+
}
7473+
]
7474+
},
7475+
"step": "shared_references"
7476+
},
7477+
{
7478+
"get_solution": "program",
7479+
"page": "Copying Dictionaries",
7480+
"program": [
7481+
"d1 = {'a': 1, 'b': 2}",
7482+
"d2 = d1.copy() # Create a separate copy",
7483+
"",
7484+
"print(\"d1 before:\", d1)",
7485+
"print(\"d2 before:\", d2)",
7486+
"print(\"Are they the same object?\", d1 is d2)",
7487+
"",
7488+
"d2['c'] = 3 # Modify the copy",
7489+
"",
7490+
"print(\"d1 after:\", d1) # Is d1 affected now?",
7491+
"print(\"d2 after:\", d2)"
7492+
],
7493+
"response": {
7494+
"passed": true,
7495+
"prediction": {
7496+
"answer": "d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? False\nd1 after: {'a': 1, 'b': 2}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7497+
"choices": [
7498+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? True\nd1 after: {'a': 1, 'b': 2, 'c': 3}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7499+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? False\nd1 after: {'a': 1, 'b': 2, 'c': 3}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7500+
"d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? False\nd1 after: {'a': 1, 'b': 2}\nd2 after: {'a': 1, 'b': 2, 'c': 3}",
7501+
"Error"
7502+
]
7503+
},
7504+
"result": [
7505+
{
7506+
"text": "d1 before: {'a': 1, 'b': 2}\nd2 before: {'a': 1, 'b': 2}\nAre they the same object? False\nd1 after: {'a': 1, 'b': 2}\nd2 after: {'a': 1, 'b': 2, 'c': 3}\n",
7507+
"type": "stdout"
7508+
}
7509+
]
7510+
},
7511+
"step": "making_copies"
7512+
},
7513+
{
7514+
"get_solution": "program",
7515+
"page": "Copying Dictionaries",
7516+
"program": [
7517+
"def positive_stock(stock):",
7518+
" result = {}",
7519+
" for item in stock:",
7520+
" quantity = stock[item]",
7521+
" if quantity > 0:",
7522+
" result[item] = quantity",
7523+
" return result"
7524+
],
7525+
"response": {
7526+
"passed": true,
7527+
"result": []
7528+
},
7529+
"step": "positive_stock_exercise"
7530+
},
7531+
{
7532+
"get_solution": "program",
7533+
"page": "Copying Dictionaries",
7534+
"program": [
7535+
"def add_item(item, quantities):",
7536+
" new_quantities = quantities.copy()",
7537+
" new_quantities[item] = new_quantities[item] + 1",
7538+
" return new_quantities"
7539+
],
7540+
"response": {
7541+
"passed": true,
7542+
"result": []
7543+
},
7544+
"step": "add_item_exercise"
74407545
}
74417546
]

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy