Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit993c9ba

Browse files
committed
Add page MakingTheBoard
1 parentd9e094e commit993c9ba

File tree

4 files changed

+317
-4
lines changed

4 files changed

+317
-4
lines changed

‎core/chapters/c11_tic_tac_toe_project.py‎

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
fromtextwrapimportdedent
77
fromtypingimportList
88

9+
fromcore.exercisesimportassert_equal,ExerciseError
910
fromcore.textimportExerciseStep,Page,MessageStep,Disallowed,VerbatimStep
1011
fromcore.utilsimportreturns_stdout
1112

@@ -1573,3 +1574,224 @@ def generate_inputs(cls):
15731574
final_text="""
15741575
Brilliant! You're almost ready to put it all together, keep going!
15751576
"""
1577+
1578+
1579+
classMakingTheBoard(Page):
1580+
title="Making the Board"
1581+
1582+
classnaive_make_board(VerbatimStep):
1583+
"""
1584+
So far the board has been provided for you as a nested list.
1585+
But for the full program, you need to create it yourself.
1586+
Should be easy, right? Here's some code to do that:
1587+
1588+
__copyable__
1589+
__program_indented__
1590+
1591+
It's close, but there's a subtle problem with it.
1592+
Make sure you understand the code,
1593+
and bonus points if you can spot the bug!
1594+
If not, don't feel bad or waste too much time on it.
1595+
"""
1596+
1597+
defprogram(self):
1598+
defmake_board(size):
1599+
row= []
1600+
for_inrange(size):
1601+
row.append(' ')
1602+
board= []
1603+
for_inrange(size):
1604+
board.append(row)
1605+
returnboard
1606+
1607+
deftest():
1608+
board=make_board(3)
1609+
assert_equal(board, [
1610+
[' ',' ',' '],
1611+
[' ',' ',' '],
1612+
[' ',' ',' '],
1613+
])
1614+
board[0][0]='X'
1615+
assert_equal(board, [
1616+
['X',' ',' '],
1617+
[' ',' ',' '],
1618+
[' ',' ',' '],
1619+
])
1620+
1621+
test()
1622+
1623+
classfix_make_board(ExerciseStep):
1624+
"""
1625+
Can you see what happened?
1626+
1627+
Every row got an `'X'` in the first position!
1628+
It's as if the code actually did this:
1629+
1630+
board[0][0] = 'X'
1631+
board[1][0] = 'X'
1632+
board[2][0] = 'X'
1633+
1634+
Try and figure out what's wrong by yourself.
1635+
But again, it's tricky, so don't drive yourself crazy over it.
1636+
1637+
If you want, here's some hints:
1638+
1639+
- Try running the code through some debuggers.
1640+
- Experiment. Make changes to the code and see what happens.
1641+
- No, the code didn't do 3 assignments like I suggested above. There was just one list assignment.
1642+
- There's no hidden loops or anything.
1643+
- How many lists does `board` contain? 3?
1644+
- The previous page has a subtle hint at what happened.
1645+
- There is a page from a previous chapter where this kind of problem is explained directly.
1646+
- Specifically [this page](#EqualsVsIs).
1647+
- Try running the code with Python Tutor.
1648+
1649+
OK, if you're ready, here's the answer.
1650+
1651+
The list `row` was only created once, and reused several times.
1652+
`board` contains the same list three times. Not copies, just one list in three places.
1653+
It's like it did this:
1654+
1655+
board = [row, row, row]
1656+
1657+
Which means that this code:
1658+
1659+
board[0][0] = 'X'
1660+
1661+
is equivalent to:
1662+
1663+
row[0] = 'X'
1664+
1665+
which affects 'all the lists' in `board` because they're all just the one list `row`.
1666+
In other words, the above line is *also* equivalent to each of these two lines:
1667+
1668+
board[1][0] = 'X'
1669+
board[2][0] = 'X'
1670+
1671+
because `row` is `board[0]`, `board[1]`, and `board[2]` all at once.
1672+
1673+
Your job now is to fix `make_board` to not have this problem.
1674+
It should still return a list of length `size` where each
1675+
element is also list of length `size` where each element is the string `' '`.
1676+
The sublists should all be separate list objects, not the same
1677+
list repeated.
1678+
"""
1679+
1680+
parsons_solution=True
1681+
1682+
hints="""
1683+
The existing code is almost correct.
1684+
There are several ways to solve this.
1685+
Some solutions involve adding something small.
1686+
You can also rearrange the code without adding or removing anything (except spaces).
1687+
The problem is that a single list `row` is used several times.
1688+
So one solution is to make copies of `row` which will all be separate.
1689+
Another solution is to make a new `row` from scratch each time.
1690+
There are a few ways to copy a list in Python with a tiny bit of code.
1691+
Making a new row each time can be done by just rearranging the code.
1692+
"""
1693+
1694+
defsolution(self):
1695+
defmake_board(size):
1696+
board= []
1697+
for_inrange(size):
1698+
row= []
1699+
for_inrange(size):
1700+
row.append(' ')
1701+
board.append(row)
1702+
returnboard
1703+
1704+
returnmake_board
1705+
1706+
tests= {
1707+
2: [
1708+
[' ',' '],
1709+
[' ',' ']
1710+
],
1711+
3: [
1712+
[' ',' ',' '],
1713+
[' ',' ',' '],
1714+
[' ',' ',' '],
1715+
],
1716+
}
1717+
1718+
@classmethod
1719+
defgenerate_inputs(cls):
1720+
returndict(size=randint(4,12))
1721+
1722+
@classmethod
1723+
defcheck_result(cls,func,inputs,expected_result):
1724+
result=super().check_result(func,inputs,expected_result)
1725+
iflen(result)!=len(set(map(id,result))):
1726+
raiseExerciseError("The sublists in the result are not all separate objects")
1727+
1728+
final_text="""
1729+
Well done!
1730+
1731+
This could be solved by moving the first loop inside the second to make a new `row` each time:
1732+
1733+
def make_board(size):
1734+
board = []
1735+
for _ in range(size):
1736+
row = []
1737+
for _ in range(size):
1738+
row.append(' ')
1739+
board.append(row)
1740+
return board
1741+
1742+
Another way is to make a copy of `row` each time, e.g. keep the original code but change one line:
1743+
1744+
board.append(row.copy())
1745+
1746+
You can also copy `row` with `row[:]` or `list(row)`. But it's important to know that
1747+
all these methods make a *shallow copy* of the list.
1748+
That means they copy the whole list at the top level, without making copies of each element.
1749+
That's fine in this case where `row` only contains strings which can't be modified
1750+
and don't need copying. But if the elements are mutable objects like lists,
1751+
as is the case with `board`, you may run into the same problem again.
1752+
Here's an example:
1753+
1754+
__copyable__
1755+
def make_board(size):
1756+
row = []
1757+
for _ in range(size):
1758+
row.append(' ')
1759+
board = []
1760+
for _ in range(size):
1761+
board.append(row.copy())
1762+
return board
1763+
1764+
def make_cube(size):
1765+
cube = []
1766+
board = make_board(size)
1767+
for _ in range(size):
1768+
cube.append(board.copy())
1769+
return cube
1770+
1771+
def test():
1772+
cube = make_cube(2)
1773+
print(cube)
1774+
cube[0][0][0] = 'X'
1775+
print(cube)
1776+
print(cube[0] is cube[1])
1777+
print(cube[0][0] is cube[0][1])
1778+
print(cube[0][0] is cube[1][0])
1779+
1780+
test()
1781+
1782+
Here each element of `cube` is a separate list, a copy of `board`.
1783+
And within each of those copies, each element is also a separate list, a copy of `row`.
1784+
But the shallow copies of `board` all have the same first element as each other (the first copy of `row`),
1785+
the same second element, and so on.
1786+
Changing `make_board` won't fix anything here, the solution is to either:
1787+
1788+
- Call `make_board` repeatedly to make a new `board` each time, or
1789+
- Use the `deepcopy` function instead of `board.copy()`.
1790+
`deepcopy` makes copies at every level of nested objects.
1791+
1792+
If you're still confused, don't worry.
1793+
This is just preparing you to deal with your code behaving weirdly in the future.
1794+
You're not required to understand this right now and this lesson will still be valuable.
1795+
1796+
Either way, we're ready to make the full game. You can do it!
1797+
"""

‎core/exercises.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ def check_result(func, inputs, expected_result):
8585
exceptExceptionase:
8686
result=format_exception_string()
8787

88-
result=clean_result(result)
88+
cleaned_result=clean_result(result)
8989
expected_result=clean_result(expected_result)
9090

91-
ifresult!=expected_result:
91+
ifcleaned_result!=expected_result:
9292
inputs.pop("stdin_input",None)
9393
ifinputs:
9494
message=f"""\
@@ -102,13 +102,14 @@ def check_result(func, inputs, expected_result):
102102

103103
message+=f"""
104104
105-
{result}
105+
{cleaned_result}
106106
107107
when it should output:
108108
109109
{expected_result}
110110
"""
111111
raiseExerciseError(message)
112+
returnresult
112113

113114

114115
defgenerate_string(length=None):

‎core/text.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ def test_exercise(cls, func, values):
537537

538538
@classmethod
539539
defcheck_result(cls,func,inputs,result):
540-
check_result(func,inputs,result)
540+
returncheck_result(func,inputs,result)
541541

542542
@classmethod
543543
defgenerate_inputs(cls):

‎tests/test_transcript.json‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16299,5 +16299,95 @@
1629916299
]
1630016300
},
1630116301
"step":"nested_assignment_input"
16302+
},
16303+
{
16304+
"page":"Making the Board",
16305+
"program": [
16306+
"def make_board(size):",
16307+
" row = []",
16308+
" for _ in range(size):",
16309+
" row.append(' ')",
16310+
" board = []",
16311+
" for _ in range(size):",
16312+
" board.append(row)",
16313+
" return board",
16314+
"",
16315+
"def test():",
16316+
" board = make_board(3)",
16317+
" assert_equal(board, [",
16318+
" [' ', ' ', ' '],",
16319+
" [' ', ' ', ' '],",
16320+
" [' ', ' ', ' '],",
16321+
" ])",
16322+
" board[0][0] = 'X'",
16323+
" assert_equal(board, [",
16324+
" ['X', ' ', ' '],",
16325+
" [' ', ' ', ' '],",
16326+
" [' ', ' ', ' '],",
16327+
" ])",
16328+
"",
16329+
"test()"
16330+
],
16331+
"response": {
16332+
"message":"",
16333+
"passed":true,
16334+
"result": [
16335+
{
16336+
"color":"white",
16337+
"text":"OK"
16338+
},
16339+
{
16340+
"color":"white",
16341+
"text":"\n"
16342+
},
16343+
{
16344+
"color":"white",
16345+
"text":"Error! [['X', ' ', ' '], ['X', ' ', ' '], ['X', ' ', ' ']] != [['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]"
16346+
},
16347+
{
16348+
"color":"white",
16349+
"text":"\n"
16350+
},
16351+
{
16352+
"color":"white",
16353+
"text":">>>"
16354+
}
16355+
]
16356+
},
16357+
"step":"naive_make_board"
16358+
},
16359+
{
16360+
"get_solution": [
16361+
"def make_board(size):",
16362+
" board = []",
16363+
" for _ in range(size):",
16364+
" row = []",
16365+
" for _ in range(size):",
16366+
" row.append(' ')",
16367+
" board.append(row)",
16368+
" return board"
16369+
],
16370+
"page":"Making the Board",
16371+
"program": [
16372+
"def make_board(size):",
16373+
" board = []",
16374+
" for _ in range(size):",
16375+
" row = []",
16376+
" for _ in range(size):",
16377+
" row.append(' ')",
16378+
" board.append(row)",
16379+
" return board"
16380+
],
16381+
"response": {
16382+
"message":"",
16383+
"passed":true,
16384+
"result": [
16385+
{
16386+
"color":"white",
16387+
"text":">>>"
16388+
}
16389+
]
16390+
},
16391+
"step":"fix_make_board"
1630216392
}
1630316393
]

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp