Some of this week's topics are covered in Problem Solving with Algorithms:

In the last lecture we learned about depth-first
search and breadth-first search, which are fundamental algorithms.
In that lecture we used these
algorithms to explore graphs. However these algorithms are more
broadly applicable: we can use them to solve many problems in which
we search through a **state space**. These
problems have a graph-like structure. In each such problem, there is
an **initial
state**
and a function that maps each state to a list of its possible
**successor
states**.
We are searching for some path that leads from the initial state to
a **goal
state**.

For example, consider the following device:

There are four wheels, each of which turn left and right. At every moment, the device displays a four-digit number at the top.

Consider the following problem. Initially the device displays 8056. In each move, we turn one of the wheels left or right by one position. Our goal is to reach the position 8157 using the smallest possible number of moves. However, we may not pass through the positions 8057, 8066, 8156, or 9056, which are forbidden.

This is a state space search problem. We can think of the state space as an abstract graph. Each state is a vertex of the graph, and there is an edge from each state to each of its successors. In this problem this abstract graph has 10,000 vertices. Every vertex has degree 8, since each state has 8 possible successors because each move turns one of 4 wheels in one of 2 directions. Here is a picture of a small subset of this graph, with the forbidden states in red:

Let's write a function wheel_dist() to solve this problem in general: given a start position, a goal position and a list of forbidden positions it will find the shortest possible path from the start to the goal. We will use a breadth-first search, since we want the shortest possible path and a depth-first search cannot find that.

We must first choose a representation for states. Various representations are possible: for example, we could represent the state 8056 by an integer, or by the list [8, 0, 5, 6], or the tuple (8, 0, 5, 6), or by the string '8056'. We will choose the last of these possibilities: each state will be a string of digits.

Let's first write a function that can generate a list of successor states of each state:

def successors(state): ret = [] for wheel in range(4): for dir in [1, -1]: digit = (int(state[wheel]) + dir) % 10 ret.append(state[:wheel] + str(digit) + state[wheel + 1:]) return ret

Now we can write the main wheel_dist() function. It looks very much like our previous implementation of breadth-first search, but we call the successors() function in place of examining a concrete graph in memory. In this function we use a dictionary 'dist' that maps each state to its distance from the start state. We don't need a separate visited set, since the keys of 'dist' are exactly the states that we have visited:

def wheel_dist(start, goal, forbidden): queue = deque() dist = {} queue.append(start) dist[start] = 0 while len(queue) > 0: s = queue.popleft() for t in successors(s): if t not in dist and t not in forbidden: queue.append(t) dist[t] = dist[s] + 1 return dist[goal]

The function can easily solve our original problem:

>>> wheel_dist('8056', '8157', ['9056', '8156', '8066', '8057']) 4

Arithmetic expressions are composed from numbers
and operators that act on those numbers. In this section we will use
the operators `+`

, `-`

, `*`

and `/`

,
the last of which denotes integer division.

In traditional mathematical notation, these
operators are infix operators: they are written between the values
that they act on (which are called operands). For example, we write ```
2
+ 2
```

, or `8 - 7`

. In this last expression, 8 and 7
are the operands.

Here are some arithmetic expressions written using infix notation:

```
((4
+ 5) * (2 + 1))
```

```
((7
/ 2)
```

```
```

`-`

```
```

`1)`

We may choose an alternative syntax that uses
**prefix notation**, in which we write each operator before its
operands. For example, we write `+ 2 4`

to mean the sum of
2 and 4, or `/ 8 2`

to mean 8 divided by 2. Here are the
above expressions rewritten in prefix notation:

* + 4 5 + 2 1 - / 7 2 1

Or we may use **postfix notation**, in which operators come after
both operands: we might write `4 5 +`

to mean the sum of 4
and 5. Here are the above expressions in postfix notation:

4 5 + 2 1 + * 7 2 / 1 -

In infix notation we must write parentheses to distinguish
expressions that would otherwise be ambiguous. For example, ```
4 +
5 * 9
```

could mean either `((4 + 5) * 9)`

or ```
(4
+ (5 * 9))
```

. (Another way to disambiguate expressions is using
**operator precedence**. For example, `*`

is generally
considered to have higher precedence than `+`

. But in this
discussion we will assume that no such precedence exists.)

In prefix or postfix notation there is no need
either for parentheses or operator precedence, because expressions
are inherently unambiguous. For example, the prefix expression ```
*
+ 4 5 9
```

is equivalent to `((4 + 5) * 9)`

, and the
prefix expression `+ 4 * 5 9`

is equivalent to ```
(4 +
(5 * 9))
```

. There is no danger of confusing these in prefix form
even without parentheses.

We may
store expressions using an **expression tree**,
which is a form of **abstract syntax tree**.
An expression tree reflects an expression’s hierarchical structure.
For example, here is an expression tree for the infix expression ((3
+ 4) * (2 + 5)):

Note that this
expression tree also corresponds to the prefix expression * + 3 4 + 2
5, or the postfix expression 3 4 + 2 5 + *. Equivalent expressions in
infix, prefix or postfix form will always have the same tree! That’s
because this tree reflects the **abstract syntax** of
the expression, which is independent of **concrete syntax
**which defines how expressions
are written as strings of symbols.

We can easily
store expression trees using
objects in Python.
Every interior node of the
tree represents
a **binary
operation**, and every leaf is an
integer. Here is a class
for interior
nodes:

class BinOp: def __init__(self, left, op, right): self.left = left self.op = op self.right = right

This may remind you of the class for binary search trees that we saw a few weeks ago. However, note some important differences. In our representation of binary search trees, we held values (typically numbers) in every node, and the left and right children of every left node were None. In these expression trees, the leaves are Python integers.

We can build the expression tree in the picture above as follows:

l = BinOp(3, '+', 4) r = BinOp(2, '+', 5) top = BinOp(l, '*', r)

If we have an expression tree in memory, we may wish to evaluate
it. To **evaluate** an
expression means to determine its value. For example, when we
evaluate the infix expression (2 + (3 * 4)), we get the value 14.

We first write a function that can apply an operator to two numbers:

def eval_op(x, op, y): if op == '+': return x + y if op == '-': return x - y if op == '*': return x * y if op == '/': return x // y assert False, 'unknown operator: ' + op

Now we can evaluate an expression tree with a straightforward recursive function:

def eval(e): if isinstance(e, int): return e assert isinstance(e, BinOp) l = eval(e.left) r = eval(e.right) return eval_op(l, e.op, r)

Given an expression tree, it’s not hard to convert it to a string representation in either infix, prefix or postfix form. For example, we can generate a prefix expression as follows:

def to_prefix(e): if isinstance(e, int): return str(e) l = to_prefix(e.left) r = to_prefix(e.right) return f'{e.op} {l} {r}'

Or we may generate an infix expression from a tree:

def to_infix(e): if isinstance(e, int): return str(e) l = to_infix(e.left) r = to_infix(e.right) return f'({l} {e.op} {r})'

Notice that we fully parenthesize each subexpression in a generated infix expression. If we did not do this, then we might generate a string such as '3 + 4 * 5 + 2' when we really meant '((3 + 4) * (5 + 2))'. In a prefix expression, parentheses are unnecessary since prefix expressions are unambiguous.

Our discussion of expression syntax to this point has been somewhat informal. We will now formally define the syntax of arithmetic expressions, which will help us in the next section. For simplicity, we will assume that

all numbers consist of only a single digit

the only operators are +, -, *, / meaning integer addition, subtraction, multiplication and division

expressions have no embedded spaces

We can define the **syntax** of infix
arithmetic expressions using the following **context-free grammar**:

digit → '0' | '1' |
'2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

op → '+' | '-' |
'*' | '/'

expr → digit | '(' expr op expr ')'

Context-free grammars are commonly used to define
programming languages. A grammar contains **non-terminal symbols**
(such as digit, op, expr) and **terminal symbols** (such as '0',
'1', '2', …, '+', '-', '*', '/'), which are characters. A grammar
contains a set of **production rules** that define how each
non-terminal symbol can be constructed from other symbols. These
rules collectively define which strings of terminal symbols are
syntactically **valid**.

For example, `((4 + 5) * 9)`

is a valid
expression in the syntax defined by this grammar, because it can be
constructed from the top-level non-terminal `expr`

by
successively replacing symbols using the production rules:

expr

→ `(`

expr op expr `)`

→ `(`

expr op digit `)`

→
`(`

expr op `9)`

→ `(`

expr```
* 9)
```

→ `((`

expr op expr`) * 9)`

→
`((`

expr op digit`) * 9)`

→ `((`

expr
op` 5) * 9)`

→ `((`

expr ```
+ 5) *
9)
```

→ `((`

digit `+ 5) * 9)`

→
`((4 + 5) * 9)`

You will learn more about context-free grammars in more advanced courses, but it’s worth taking this first look at them now because they are so commonly used to specify languages.

To modify our grammar to specify the syntax of prefix expressions, we need change only the last production rule above:

expr → digit | op expr expr

Or, for postfix expressions:

expr → digit | expr expr op

Note again that the prefix and postfix expression languages have no parentheses.

We would
now like to **parse** arithmetic expressions defined by the
grammar above. Given a string containing a prefix, infix, or postfix
expression, we'd like to determine the expression's structure and (a)
generate an expression tree or (b) evaluate the expression directly.

In our parsers we will need to read characters one at a time from a string. So let's first write a Reader class that lets us do that:

class Reader: def __init__(self, s): self.s = s.replace(' ', '') # ignore spaces self.i = 0 def next(self): c = self.s[self.i] self.i += 1 return c

It works:

>>> r = Reader('+ 2 3') >>> r.next() '+' >>> r.next() '2' >>> r.next() '3'

Let's write a function that
can parse
an arithmetic expression in **prefix** notation and generate an
expression tree.

def parse_prefix(s): reader = Reader(s) def parse(): c = reader.next() if c.isdigit(): return int(c) assert c in '+-*/' left = parse() right = parse() return BinOp(left, c, right) return parse()

We can now parse an expression and evaluate it:

>>> e = parse_prefix('+ * 2 3 * 4 5') >>> eval(e) 26

Notice that the structure of the nested function parse() above follows the grammar rule for prefix expressions:

expr → digit | op expr expr

We
have written a **recursive-descent parser** for an extremely
simple **language**, i.e. the language of arithmetic expressions
with operators +, -, *, / in prefix notation. Many interpreters and
compilers for real programming languages such as Python also parse
code using recursive-descent parsers, though of course those
languages are far more complex.

If we wish to evaluate a prefix expression directly rather than generating a tree, we can do so by changing the last line of parse() above to

return eval_op(left, c, right)

Parsing an **infix** expression is not much more difficult. Recall
the grammar rule for producing infix expressions:

expr → digit | '(' expr op expr ')'

We now write a recursive function that mirrors this rule:

def parse_infix(s): reader = Reader(s) def parse(): c = reader.next() if c.isdigit(): return int(c) assert c == '(' left = parse() op = reader.next() assert op in '+-*/' right = parse() assert reader.next() == ')' return BinOp(left, op, right) return parse()

As with prefix expressions, we can make a trivial change to this function to evaluate an infix expression directly rather than generating a tree:

return eval_op(left, op, right)

To parse a **postfix** expression, we cannot easily use recursion.
Instead, the most
straightforward approach is
to use a stack. As we read a postfix expression from left to
right, each time we see a number we push it onto the stack. When we
see an operator, we pop two numbers from the stack, apply the
operator and then push the result back onto the stack. For example,
in evaluating the postfix expression 4
5 + 2 1 + * we would perform the following operations:

push 4

push 5

pop 4 and 5, push 9

push 2

push 1

pop 2 and 1, push 3

pop 9 and 3, push 27

When we finish reading the expression, if it is valid then the stack will contain a single number, which is the result of our expression evaluation.

To generate a expression tree from a postfix expression, we can use a stack of pointers to expression trees. When we read an operand, we pop two subtrees from the stack, combine them into a BinOp object, then push it back on the stack.

In the tutorial we solved this exercise:

*Consider
a limping knight on a chessboard. On odd moves, it moves like a pawn,
i.e. one square forward (or stays in place if it is at the top of the
board). On even moves, it moves like a normal knight in chess. Write
a function limping_dist(start, end) that returns the smallest number
of moves that the knight must make to go from a given starting square
to a given ending square. Assume that squares are pairs of
coordinates, i.e. (1, 1) is the upper-left corner of the chessboard
and (8, 8) is the lower-right corner.*

This is a state space search problem. We'll represent each state by a tuple ((x, y), odd), where (x, y) is the knight's position and 'odd' is a boolean value that is True if the knight is about to make an odd move (i.e. move like a pawn).

from collections import deque moves = [(2, 1), (2, -1), (1, 2), (1, -2), (-1, 2), (-1, -2), (-2, 1), (-2, -1)] def successors(s): (x, y), odd = s if odd: # pawn move return [((x, y - 1 if y > 1 else 1), False)] else: ret = [] for dx, dy in moves: if 1 <= x + dx <= 8 and 1 <= y + dy <= 8: ret.append( ((x + dx, y + dy), True) ) return ret def limping_dist(start, goal): queue = deque() dist = {} initial_state = (start, False) queue.append(initial_state) dist[initial_state] = 0 while len(queue) > 0: s = queue.popleft() for t in successors(s): if t not in dist: dist[t] = dist[s] + 1 if t[0] == goal: return dist[t] queue.append(t) return -1 # unreachable