Skip to content

Commit 3441696

Browse files
author
Shati Patel
committed
Apply suggestions from code review
1 parent c156d6a commit 3441696

File tree

3 files changed

+67
-49
lines changed

3 files changed

+67
-49
lines changed

docs/language/learn-ql/ql-etudes/river-crossing-1.ql

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,40 @@ class Shore extends string {
3232

3333
/** A record of where everything is. */
3434
class State extends string {
35-
Shore man;
35+
Shore manShore;
3636

37-
Shore goat;
37+
Shore goatShore;
3838

39-
Shore cabbage;
39+
Shore cabbageShore;
4040

41-
Shore wolf;
41+
Shore wolfShore;
4242

43-
State() { this = man + "," + goat + "," + cabbage + "," + wolf }
43+
State() { this = manShore + "," + goatShore + "," + cabbageShore + "," + wolfShore }
4444

4545
State ferry(Cargo cargo) {
46-
cargo = "Nothing" and result = man.other() + "," + goat + "," + cabbage + "," + wolf
46+
cargo = "Nothing" and
47+
result = manShore.other() + "," + goatShore + "," + cabbageShore + "," + wolfShore
4748
or
48-
cargo = "Goat" and result = man.other() + "," + goat.other() + "," + cabbage + "," + wolf
49+
cargo = "Goat" and
50+
result = manShore.other() + "," + goatShore.other() + "," + cabbageShore + "," + wolfShore
4951
or
50-
cargo = "Cabbage" and result = man.other() + "," + goat + "," + cabbage.other() + "," + wolf
52+
cargo = "Cabbage" and
53+
result = manShore.other() + "," + goatShore + "," + cabbageShore.other() + "," + wolfShore
5154
or
52-
cargo = "Wolf" and result = man.other() + "," + goat + "," + cabbage + "," + wolf.other()
55+
cargo = "Wolf" and
56+
result = manShore.other() + "," + goatShore + "," + cabbageShore + "," + wolfShore.other()
5357
}
5458

5559
/**
56-
* Holds if predator and prey are on the same shore and the man
57-
* is not present.
60+
* Holds if eating occurs. This happens when predator and prey are on the same shore
61+
* and the man is not present.
5862
*/
59-
predicate eats(Shore predator, Shore prey) { predator = prey and man != predator }
63+
predicate eating(Shore predatorShore, Shore preyShore) {
64+
predatorShore = preyShore and manShore != predatorShore
65+
}
6066

6167
/** Holds if nothing gets eaten in this state. */
62-
predicate isSafe() { not (eats(goat, cabbage) or eats(wolf, goat)) }
68+
predicate isSafe() { not (eating(goatShore, cabbageShore) or eating(wolfShore, goatShore)) }
6369

6470
/** Returns the state that is reached after safely ferrying a cargo item. */
6571
State safeFerry(Cargo cargo) { result = this.ferry(cargo) and result.isSafe() }

docs/language/learn-ql/ql-etudes/river-crossing.ql

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/**
22
* @name River crossing puzzle
33
* @description This implements the classical puzzle where a man is trying to
44
* ferry a goat, a cabbage, and a wolf across a river.
@@ -38,41 +38,47 @@ class Shore extends string {
3838
}
3939

4040
/** Renders the state as a string. */
41-
string renderState(Shore man, Shore goat, Shore cabbage, Shore wolf) {
42-
result = man + "," + goat + "," + cabbage + "," + wolf
41+
string renderState(Shore manShore, Shore goatShore, Shore cabbageShore, Shore wolfShore) {
42+
result = manShore + "," + goatShore + "," + cabbageShore + "," + wolfShore
4343
}
4444

4545
/** A record of where everything is. */
4646
class State extends string {
47-
Shore man;
47+
Shore manShore;
4848

49-
Shore goat;
49+
Shore goatShore;
5050

51-
Shore cabbage;
51+
Shore cabbageShore;
5252

53-
Shore wolf;
53+
Shore wolfShore;
5454

55-
State() { this = renderState(man, goat, cabbage, wolf) }
55+
State() { this = renderState(manShore, goatShore, cabbageShore, wolfShore) }
5656

5757
/** Returns the state that is reached after ferrying a particular cargo item. */
5858
State ferry(Cargo cargo) {
59-
cargo = "Nothing" and result = renderState(man.other(), goat, cabbage, wolf)
59+
cargo = "Nothing" and
60+
result = renderState(manShore.other(), goatShore, cabbageShore, wolfShore)
6061
or
61-
cargo = "Goat" and result = renderState(man.other(), goat.other(), cabbage, wolf)
62+
cargo = "Goat" and
63+
result = renderState(manShore.other(), goatShore.other(), cabbageShore, wolfShore)
6264
or
63-
cargo = "Cabbage" and result = renderState(man.other(), goat, cabbage.other(), wolf)
65+
cargo = "Cabbage" and
66+
result = renderState(manShore.other(), goatShore, cabbageShore.other(), wolfShore)
6467
or
65-
cargo = "Wolf" and result = renderState(man.other(), goat, cabbage, wolf.other())
68+
cargo = "Wolf" and
69+
result = renderState(manShore.other(), goatShore, cabbageShore, wolfShore.other())
6670
}
6771

6872
/**
69-
* Holds if predator and prey are on the same shore and the man
70-
* is not present.
73+
* Holds if eating occurs. This happens when predator and prey are on the same shore
74+
* and the man is not present.
7175
*/
72-
predicate eats(Shore predator, Shore prey) { predator = prey and man != predator }
76+
predicate eating(Shore predatorShore, Shore preyShore) {
77+
predatorShore = preyShore and manShore != predatorShore
78+
}
7379

7480
/** Holds if nothing gets eaten in this state. */
75-
predicate isSafe() { not (eats(goat, cabbage) or eats(wolf, goat)) }
81+
predicate isSafe() { not (eating(goatShore, cabbageShore) or eating(wolfShore, goatShore)) }
7682

7783
/** Returns the state that is reached after safely ferrying a cargo item. */
7884
State safeFerry(Cargo cargo) { result = this.ferry(cargo) and result.isSafe() }
@@ -112,3 +118,4 @@ class GoalState extends State {
112118
from string path
113119
where any(InitialState i).reachesVia(path, _) = any(GoalState g)
114120
select path
121+

docs/language/learn-ql/ql-etudes/river-crossing.rst

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ a piece of cargo.
4747
Second, any item can be on one of two shores. Let's call these the "left shore" and the "right shore".
4848
Define a class ``Shore`` containing ``"Left"`` and ``"Right"``.
4949

50-
It would be helpful to express "the other shore". You can do this by defining a member predicate
50+
It would be helpful to express "the other shore" to model moving from one side of the river to the other.
51+
You can do this by defining a member predicate
5152
``other`` in the class ``Shore`` such that ``"Left".other()`` returns ``"Right"`` and vice versa.
5253

5354
.. container:: toggle
@@ -59,8 +60,8 @@ It would be helpful to express "the other shore". You can do this by defining a
5960
.. literalinclude:: river-crossing.ql
6061
:lines: 25-38
6162

62-
We also want a way to keep track of where the man, the goat, the cabbage, and the wolf arecall this combined
63-
information the "state". Define a class ``State`` that encodes this information.
63+
We also want a way to keep track of where the man, the goat, the cabbage, and the wolf are at any point. We can call this combined
64+
information the "state". Define a class ``State`` that encodes the location of each piece of cargo.
6465
For example, if the man is on the left shore, the goat on the right shore, and the cabbage and wolf on the left
6566
shore, the state should be ``Left, Right, Left, Left``.
6667

@@ -74,9 +75,10 @@ temporary variables in the body of a class are called `fields <https://help.semm
7475
*Show/hide code*
7576

7677
.. literalinclude:: river-crossing-1.ql
77-
:lines: 33-43,84
78+
:lines: 33-43,90
7879

79-
We are interested in two particular states, namely the initial state and the goal state.
80+
We are interested in two particular states, namely the initial state and the goal state,
81+
which we have to achieve to solve the puzzle.
8082
Assuming that all items start on the left shore and end up on the right shore, define
8183
``InitialState`` and ``GoalState`` as subclasses of ``State``.
8284

@@ -87,7 +89,7 @@ Assuming that all items start on the left shore and end up on the right shore, d
8789
*Show/hide code*
8890

8991
.. literalinclude:: river-crossing-1.ql
90-
:lines: 86-94
92+
:lines: 92-100
9193

9294
.. pull-quote::
9395

@@ -105,12 +107,12 @@ Using the above note, the QL code so far looks like this:
105107
*Show/hide code*
106108

107109
.. literalinclude:: river-crossing.ql
108-
:lines: 14-55,100-110
110+
:lines: 14-55,105-115
109111

110112
Model the action of "ferrying"
111113
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
112114

113-
The basic act of ferrying moves the man and a cargo item to the other shore,
115+
The basic act of ferrying moves the man and one cargo item to the other shore,
114116
resulting in a new state.
115117

116118
Write a member predicate (of ``State``) called ``ferry``, that specifies what happens to the state
@@ -123,13 +125,13 @@ after ferrying a particular cargo. (Hint: Use the predicate ``other``.)
123125
*Show/hide code*
124126

125127
.. literalinclude:: river-crossing.ql
126-
:lines: 57-66
128+
:lines: 57-70
127129

128130
Of course, not all ferrying actions are possible. Add some extra conditions to describe when a ferrying
129-
action is "safe", that is, it doesn't lead to a state where the goat or the cabbage get eaten.
131+
action is "safe". That is, it doesn't lead to a state where the goat or the cabbage get eaten.
130132
For example, follow these steps:
131133

132-
#. Define a predicate ``eats`` that encodes the conditions for when a "predator" is able to eat an
134+
#. Define a predicate ``eating`` that encodes the conditions for when a "predator" is able to eat an
133135
unguarded "prey".
134136
#. Define a predicate ``isSafe`` that holds when nothing gets eaten.
135137
#. Define a predicate ``safeFerry`` that restricts ``ferry`` to only include safe ferrying actions.
@@ -141,18 +143,20 @@ For example, follow these steps:
141143
*Show/hide code*
142144

143145
.. literalinclude:: river-crossing.ql
144-
:lines: 68-78
146+
:lines: 72-84
145147

146148
Find paths from one state to another
147149
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
148150

149151
The main aim of this query is to find a path, that is, a list of successive ferrying actions, to get
150-
from one state to another.
152+
from the initial state to the goal state.
151153

152-
When finding the path, you should be careful to avoid "infinite" solutions. For example, the man
154+
When finding the solution, you should be careful to avoid "infinite" paths. For example, the man
153155
could ferry the goat back and forth any number of times without ever reaching an unsafe state.
156+
Such a path would have an infinite number of river crossings without ever solving the puzzle.
154157

155-
One way to restrict to finite solutions is to define a `member predicate <https://help.semmle.com/QL/ql-handbook/types.html#member-predicates>`__
158+
One way to restrict our paths to a finite number of river crossings is to define a
159+
`member predicate <https://help.semmle.com/QL/ql-handbook/types.html#member-predicates>`__
156160
``State reachesVia(string path, int steps)``.
157161
The result of this predicate is any state that is reachable from the current state (``this``) via
158162
the given path in a specified finite number of steps.
@@ -176,10 +180,11 @@ for example ``steps <= 7``.
176180
*Show/hide code*
177181

178182
.. literalinclude:: river-crossing-1.ql
179-
:lines: 67-83
183+
:lines: 73-89
180184

181185
However, although this ensures that the solution is finite, it can still contain loops if the upper bound
182-
for ``steps`` is large.
186+
for ``steps`` is large. In other words, you could get an inefficient solution by revisiting the same state
187+
multiple times.
183188

184189
Instead of picking an arbitrary upper bound for the number of steps, you can avoid
185190
counting steps altogether. If you keep track of states that have already been visited and ensure
@@ -205,7 +210,7 @@ the given path without revisiting any previously visited states.
205210
*Show/hide code*
206211

207212
.. literalinclude:: river-crossing.ql
208-
:lines: 80-99
213+
:lines: 86-105
209214

210215
Display the results
211216
~~~~~~~~~~~~~~~~~~~
@@ -220,7 +225,7 @@ that returns the resulting path.
220225
*Show/hide code*
221226

222227
.. literalinclude:: river-crossing.ql
223-
:lines: 112-114
228+
:lines: 118-120
224229

225230
For now, the path defined in the above predicate ``reachesVia`` just lists the order of cargo items to ferry.
226231
You could tweak the predicates and the select clause to make the solution clearer. Here are some suggestions:
@@ -240,5 +245,5 @@ Here are some more example QL queries that solve the river crossing puzzle:
240245
- Solutions described in more detail: https://lgtm.com/query/4550752404102766320/
241246
- Solutions displayed in a more visual way: https://lgtm.com/query/5824364611285694673/
242247

243-
.. TODO: Add more examples
248+
.. TODO: Add more examples + check that the queries are "tidied up" and clear.
244249

0 commit comments

Comments
 (0)