讲解chessboard、辅导Java设计、讲解Scala、辅导Java

- 首页 >> Java编程

Coursework 8 (Scala)

This coursework is worth 10%. It is about searching and backtracking. The first

part is due on 29 November at 11pm; the second, more advanced part, is due on

20 December at 11pm. You are asked to implement Scala programs that solve

various versions of the Knight’s Tour Problem on a chessboard. Note the second,

more advanced, part might include material you have not yet seen in the first

three lectures.

Important:

Make sure the files you submit can be processed by just calling

scala <<filename.scala>> on the commandline.1 Use the template files

provided and do not make any changes to arguments of functions or to

any types. You are free to implement any auxiliary function you might

need.

Do not leave any test cases running in your code because this might

slow down your program! Comment test cases out before submission,

otherwise you might hit a time-out.

Do not use any mutable data structures in your submissions! They are

not needed. This means you cannot create new Arrays or ListBuffers,

for example.

Do not use return in your code! It has a different meaning in Scala than

in Java.

Do not use var! This declares a mutable variable. Only use val!

Do not use any parallel collections! No .par therefore! Our testing and

marking infrastructure is not set up for it.

Also note that the running time of each part will be restricted to a maximum of

30 seconds on my laptop: If you calculate a result once, try to avoid to calculate

the result again. Feel free to copy any code you need from files knight1.scala,

knight2.scala and knight3.scala.

Disclaimer

It should be understood that the work you submit represents your own effort!

You have not copied from anyone else. An exception is the Scala code I showed

during the lectures or uploaded to KEATS, which you can freely use.

1All major OSes, including Windows, have a commandline. So there is no good reason to not

download Scala, install it and run it on your own computer. Just do it!

1

Background

The Knight’s Tour Problem is about finding a tour such that the knight visits

every field on an n × n chessboard once. For example on a 5 × 5 chessboard, a

knight’s tour is:

4 zzzzz

3 zzzzz

2 zzzzz

1 zzzzz

0 zzzzz

0 1 2 3 4

24 11 6 17 0

19 16 23 12 7

10 5 18 1 22

15 20 3 8 13

4 9 14 21 2

This tour starts in the right-upper corner, then moves to field (3, 2), then (4, 0)

and so on. There are no knight’s tours on 2 × 2, 3 × 3 and 4 × 4 chessboards,

but for every bigger board there is.

A knight’s tour is called closed, if the last step in the tour is within a knight’s

move to the beginning of the tour. So the above knight’s tour is not closed

because the last step on field (0, 4) is not within the reach of the first step on

(4, 4). It turns out there is no closed knight’s tour on a 5 × 5 board. But there

are on a 6 × 6 board and on bigger ones, for example

zzzzzz

zzzzzz

zzzzzz

zzzzzz

zzzzzz

zzzzzz

10 5 18 25 16 7

31 26 9 6 19 24

4 11 30 17 8 15

29 32 27 0 23 20

12 3 34 21 14 1

33 28 13 2 35 22

where the 35th move can join up again with the 0th move.

If you cannot remember how a knight moves in chess, or never played chess,

below are all potential moves indicated for two knights, one on field (2, 2) (blue

moves) and another on (7, 7) (red moves):

7zzzzzzz2N

6zzzzzzzz

5zzzzzzzz

4zzzzzzzz

3zzzzzzzz

2zz2Nzzzzz

1zzzzzzzz

0zzzzzzzz

0 1 2 3 4 5 6 7

2

Reference Implementation

This Scala assignment comes with three reference implementations in form of

jar-files. This allows you to run any test cases on your own computer. For example

you can call Scala on the command line with the option -cp knight1.jar

and then query any function from the knight1.scala template file. As usual

you have to prefix the calls with CW8a, CW8b and CW8c. Since some of the calls

are time sensitive, I included some timing information. For example

$ scala -cp knight1.jar

scala> CW8a.enum_tours(5, List((0, 0))).length

Time needed: 1.722 secs.

res0: Int = 304

scala> CW8a.print_board(8, CW8a.first_tour(8, List((0, 0))).get)

Time needed: 15.411 secs.

51 46 55 44 53 4 21 12

56 43 52 3 22 13 24 5

47 50 45 54 25 20 11 14

42 57 2 49 40 23 6 19

35 48 41 26 61 10 15 28

58 1 36 39 32 27 18 7

37 34 31 60 9 62 29 16

0 59 38 33 30 17 8 63

Hints

Part 1 useful list functions: .contains(..) checks whether an element is in

a list, .flatten turns a list of lists into just a list, _::_ puts an element on

the head of the list, .head gives you the first element of a list (make sure the

list is not Nil); a useful option function: .isDefined returns true, if an option

is Some(..); anonymous functions can be constructed using (x:Int) => ...,

this function takes an Int as an argument.

Part 2 a useful list function: .sortBy sorts a list according to a component

given by the function; a function can be tested to be tail-recursive by annotation

@tailrec, which is made available by importing scala.annotation.tailrec.

Part 1 (6 Marks)

You are asked to implement the knight’s tour problem such that the dimension

of the board can be changed. Therefore most functions will take the dimension

of the board as an argument. The fun with this problem is that even for small

chessboard dimensions it has already an incredibly large search space—finding

a tour is like finding a needle in a haystack. In the first task we want to see

3

how far we get with exhaustively exploring the complete search space for small

chessboards.

Let us first fix the basic datastructures for the implementation. The board dimension

is an integer. A position (or field) on the chessboard is a pair of integers,

like (0, 0). A path is a list of positions. The first (or 0th move) in a path is the

last element in this list; and the last move in the path is the first element. For

example the path for the 5 × 5 chessboard above is represented by

List((0, 4)

| {z }

24

, (2, 3)

| {z }

23

, ..., (3, 2)

| {z }

1

, (4, 4)

| {z }

0

)

Suppose the dimension of a chessboard is n, then a path is a tour if the length

of the path is n × n, each element occurs only once in the path, and each move

follows the rules of how a knight moves (see above for the rules).

Tasks (file knight1.scala)

(1) Implement an is_legal function that takes a dimension, a path and a

position as arguments and tests whether the position is inside the board

and not yet element in the path. [1 Mark]

(2) Implement a legal_moves function that calculates for a position all legal

onward moves. If the onward moves are placed on a circle, you should

produce them starting from “12-o’clock” following in clockwise order.

For example on an 8 × 8 board for a knight at position (2, 2) and otherwise

empty board, the legal-moves function should produce the onward

positions in this order:

List((3,4), (4,3), (4,1), (3,0), (1,0), (0,1), (0,3), (1,4))

If the board is not empty, then maybe some of the moves need to be filtered

out from this list. For a knight on field (7, 7) and an empty board,

the legal moves are

List((6,5), (5,6))

[1 Mark]

(3) Implement two recursive functions (count_tours and enum_tours). They

each take a dimension and a path as arguments. They exhaustively search

for tours starting from the given path. The first function counts all possible

tours (there can be none for certain board sizes) and the second collects

all tours in a list of paths. These functions will be called with a path containing

a single position—the starting field. They are expected to extend

this path so as to find all tours starting from the given position.

[2 Marks]

4

Test data: For the marking, the functions in (3) will be called with board sizes

up to 5 × 5. If you search for tours on a 5 × 5 board starting only from field

(0, 0), there are 304 of tours. If you try out every field of a 5 × 5-board as a

starting field and add up all tours, you obtain 1728. A 6 × 6 board is already

too large to be searched exhaustively.2

Tasks (cont.)

(4) Implement a first-function. This function takes a list of positions and

a function f as arguments; f is the name we give to this argument). The

function f takes a position as argument and produces an optional path.

So f ’s type is Pos => Option[Path]. The idea behind the first-function

is as follows:

first(Nil, f)

def = None

first(x :: xs, f)

def =

{

f(x) if f(x) ?= None

first(xs, f) otherwise

That is, we want to find the first position where the result of f is not None,

if there is one. Note that ‘inside’ first, you do not (need to) know anything

about the argument f except its type, namely Pos => Option[Path].

If you want to find out what the result of f is on a particular argument,

say x, you can just write f(x). There is one additional point however you

should take into account when implementing first: you will need to calculate

what the result of f(x) is; your code should do this only once and

for as few elements in the list as possible! Do not calculate f(x) for all

elements and then see which is the first Some.

[1 Mark]

(5) Implement a first_tour function that uses the first-function from (4),

and searches recursively for single tour. As there might not be such a tour

at all, the first_tour function needs to return a value of type Option[Path].

[1 Mark]

Testing: The first_tour function will be called with board sizes of up to 8 × 8.

Advanced Part 2 (4 Marks)

As you should have seen in Part 1, a naive search for tours beyond 8 × 8 boards

and also searching for closed tours even on small boards takes too much time.

There is a heuristic, called Warnsdorf’s Rule that can speed up finding a tour.

2For your interest, the number of tours on 6 × 6, 7 × 7 and 8 × 8 are 6637920, 165575218320,

19591828170979904, respectively.

5

This heuristic states that a knight is moved so that it always proceeds to the

field from which the knight will have the fewest onward moves. For example

for a knight on field (1, 3), the field (0, 1) has the fewest possible onward moves,

namely 2.

7 zzzzzzzz

6 zzzzzzzz

5 zzzzzzzz

4 zzzzzzzz

3 z2Nzzzzzz

2 zzzzzzzz

1 zzzzzzzz

0 zzzzzzzz

0 1 2 3 4 5 6 7

3 7

7

7

2 5

Warnsdorf’s Rule states that the moves on the board above should be tried in

the order

(0, 1),(0, 5),(2, 1),(2, 5),(3, 4),(3, 2)

Whenever there are ties, the corresponding onward moves can be in any order.

When calculating the number of onward moves for each field, we do not count

moves that revisit any field already visited.

Tasks (file knight2.scala)

(6) Write a function ordered_moves that calculates a list of onward moves

like in (2) but orders them according to Warnsdorf’s Rule. That means

moves with the fewest legal onward moves should come first (in order to

be tried out first). [1 Mark]

(7) Implement a first_closed_tour_heuristic function that searches for

a single closed tour on a 6 × 6 board. It should try out onward moves

according to the ordered_moves function from (6). It is more likely to

find a solution when started in the middle of the board (that is position

(dimension/2, dimension/2)). [1 Mark]

(8) Implement a first_tour_heuristic function for boards up to 30 × 30. It

is the same function as in (7) but searches for tours (not just closed tours).

It might be called with any field on the board as starting field.

[1 Mark]

Task (file knight3.scala)

(9) Implement a function tour_on_mega_board which is the same function

as in (8), but should be able to deal with boards up to 70 × 70 within 30

6

seconds (on my laptop). This will be tested by starting from field (0, 0).

You have to be careful to write a tail-recursive function otherwise you

will get problems with stack-overflows. Please observe the requirements

about the submissions: no tricks involving .par.

The timelimit of 30 seconds is with respect to the laptop on which the

marking will happen. You can roughly estimate how well your implementation

performs by running knight3.jar on your computer. For example

the reference implementation shows on my laptop:

$ scala -cp knight3.jar

scala> CW8c.tour_on_mega_board(70, List((0, 0)))

Time needed: 9.484 secs.

...<<long_list >>...

[1 Mark]


站长地图