mirror of
https://github.com/iritmaximus/haskell.git
synced 2026-10-12 19:13:39 +03:00
387 lines
13 KiB
Haskell
387 lines
13 KiB
Haskell
module Set9b where
|
|
|
|
import Mooc.Todo
|
|
|
|
import Data.List
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 1: In this exercise set, we'll solve the N Queens problem step by step.
|
|
-- N Queens is a generalisation of the Eight Queens problem described in
|
|
-- Wikipedia: https://en.wikipedia.org/wiki/Eight_queens_puzzle
|
|
--
|
|
-- We'll be working with a two-dimensional coordinate system for indexing the
|
|
-- queens on a (square) chessboard of arbitrary size. (1,1) represents the top
|
|
-- left corner. (1,2) is the next square on the top row, (1,3) is the one after
|
|
-- that, (2,1) is the first square on the second row, (2,2) is the second square
|
|
-- in the second row, and so on. In general, the coordinates are of the form
|
|
-- (row,column). The idea is that the following arrangement of queens on a 8x8
|
|
-- board will be encoded as the list [(1,8),(2,6),(3,4),(5,7)] of coordinates:
|
|
--
|
|
-- .......Q
|
|
-- .....Q..
|
|
-- ...Q....
|
|
-- ........
|
|
-- ......Q.
|
|
-- ........
|
|
-- ........
|
|
-- ........
|
|
--
|
|
-- The first exercise is warmup. We'll define two helper functions that we're
|
|
-- going to use later: nextRow, and nextCol. nextRow increases the row by one
|
|
-- and sets column to 1. nextCol only increases the column by one. (By analogy,
|
|
-- nextRow works like line break and carriage return while nextCol works like
|
|
-- the space bar in a typewriter.)
|
|
--
|
|
-- Examples:
|
|
-- nextRow (1,1) ==> (2,1)
|
|
-- nextRow (4,7) ==> (5,1)
|
|
-- nextCol (1,1) ==> (1,2)
|
|
-- nextCol (4,7) ==> (4,8)
|
|
--
|
|
-- Before we start, remember type aliases? We define some of them just to make
|
|
-- the roles of different function arguments clearer without adding syntactical
|
|
-- overhead:
|
|
|
|
type Row = Int
|
|
type Col = Int
|
|
type Coord = (Row, Col)
|
|
|
|
nextRow :: Coord -> Coord
|
|
nextRow (i,j) = todo
|
|
|
|
nextCol :: Coord -> Coord
|
|
nextCol (i,j) = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 2: Implement the function prettyPrint that, given the size of
|
|
-- the chessboard and a list of distinct coordinates of queens (that
|
|
-- is, a list of (row,col) pairs), prints the chessboard with the
|
|
-- queens on it. Empty squares must be printed as '.'s and queens as
|
|
-- 'Q's. The special line break character '\n' must be appended to the
|
|
-- end of each row.
|
|
--
|
|
-- Examples:
|
|
-- prettyPrint 3 [(1,1),(2,3),(3,2)] ==> "Q..\n..Q\n.Q.\n"
|
|
-- prettyPrint 3 [(2,3),(1,1),(3,2)] ==> "Q..\n..Q\n.Q.\n"
|
|
-- prettyPrint 3 [(1,3),(2,1),(3,2)] ==> "..Q\nQ..\n.Q.\n"
|
|
--
|
|
-- To see how the result looks like with the line breaks correctly printed, use
|
|
-- putStrLn in GHCI. To open this module in GHCI, run 'stack ghci Set9b.hs'.
|
|
--
|
|
-- Examples:
|
|
-- *Set9b> putStrLn $ prettyPrint 4 [(1,1),(1,4),(4,1),(4,4)]
|
|
-- Q..Q
|
|
-- ....
|
|
-- ....
|
|
-- Q..Q
|
|
--
|
|
-- *Set9b> putStrLn $ prettyPrint 4 [(4,4),(1,4),(1,1),(4,1)]
|
|
-- Q..Q
|
|
-- ....
|
|
-- ....
|
|
-- Q..Q
|
|
--
|
|
-- *Set9b> putStrLn $ prettyPrint 7 [(1,1),(2,3),(3,5),(4,7),(5,2),(6,4),(7,6)]
|
|
-- Q......
|
|
-- ..Q....
|
|
-- ....Q..
|
|
-- ......Q
|
|
-- .Q.....
|
|
-- ...Q...
|
|
-- .....Q.
|
|
--
|
|
-- Hint: Remember the function elem? elem x xs checks if the list xs contains
|
|
-- the element x, e.g. elem 1 [2,5,1] ==> True, elem 1 [2,5,2] ==> False.
|
|
--
|
|
-- Challenge: Try defining prettyPrint without elem by just iterating over all
|
|
-- coordinates one at a time. (For those who've had a course in data structures
|
|
-- and algorithms, this challenge is about finding an O(n^2) solution in terms
|
|
-- of the width (or height) n of the chess board; the naïve solution with elem
|
|
-- takes O(n^3) time. Just ignore the previous sentence, if you're not familiar
|
|
-- with the O-notation.)
|
|
|
|
type Size = Int
|
|
|
|
prettyPrint :: Size -> [Coord] -> String
|
|
prettyPrint = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 3: The task in this exercise is to define the relations sameRow, sameCol,
|
|
-- sameDiag, and sameAntidiag that check whether or not two coordinates of the
|
|
-- form (i,j) :: (Row, Col) on a table of indeterminate size are on the same
|
|
-- column, diagonal (top left to bottom right), or antidiagonal (bottom left to
|
|
-- top right) respectively. Indeterminate size of the table means that these
|
|
-- relations should work for tables of all sizes. (You may assume that all
|
|
-- coordinates will be positive.)
|
|
--
|
|
-- Examples:
|
|
-- sameRow (1,1) (1,1) ==> True
|
|
-- sameRow (1,1) (2,1) ==> False
|
|
-- sameRow (1,1) (1,2) ==> True
|
|
-- sameCol (1,1) (4,1) ==> True
|
|
-- sameCol (1,1) (4,2) ==> False
|
|
-- sameDiag (1,1) (2,2) ==> True
|
|
-- sameDiag (1,1) (1,2) ==> False
|
|
-- sameAntidiag (1,1) (1,2) ==> False
|
|
-- sameAntidiag (2,10) (5,7) ==> True
|
|
-- sameAntidiag (500,5) (5,500) ==> True
|
|
|
|
sameRow :: Coord -> Coord -> Bool
|
|
sameRow (i,j) (k,l) = todo
|
|
|
|
sameCol :: Coord -> Coord -> Bool
|
|
sameCol (i,j) (k,l) = todo
|
|
|
|
sameDiag :: Coord -> Coord -> Bool
|
|
sameDiag (i,j) (k,l) = todo
|
|
|
|
sameAntidiag :: Coord -> Coord -> Bool
|
|
sameAntidiag (i,j) (k,l) = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 4: In chess, a queen may capture another piece in the same row, column,
|
|
-- diagonal, or antidiagonal in one step. This danger zone, where pieces can be
|
|
-- captured by a queen (indicated here with the character '#') looks like this:
|
|
--
|
|
-- .#.#.#..
|
|
-- ..###...
|
|
-- ###Q####
|
|
-- ..###...
|
|
-- .#.#.#..
|
|
-- #..#..#.
|
|
-- ...#...#
|
|
-- ...#....
|
|
--
|
|
-- For multiple queens, the danger zone is the union of the danger zones for
|
|
-- individual queens. This means that all coordinates belonging to the danger
|
|
-- zones of one or more individual queens also belongs to the collective danger
|
|
-- zone of all queens on the board. For example, if we add a second queen to the
|
|
-- coordinates (4,6), the danger zone grows:
|
|
--
|
|
-- .###.#..
|
|
-- ..####.#
|
|
-- ###Q####
|
|
-- #####Q##
|
|
-- .#.####.
|
|
-- #..#.###
|
|
-- ..##.#.#
|
|
-- .#.#.#..
|
|
--
|
|
-- Implement the function danger that checks if a coordinate belongs to the
|
|
-- collective danger zone of the given list of (coordinates of) queens.
|
|
-- Graphically speaking, we want to check if the square at the given coordinates
|
|
-- looks like '.' rather than '#'. (You may assume that the given coordinate
|
|
-- will be different from all the coordinates in the stack.)
|
|
--
|
|
-- Examples:
|
|
-- danger (5,2) [] ==> False
|
|
-- danger (5,2) [(1,2)] ==> True
|
|
-- danger (5,2) [(4,3)] ==> True
|
|
-- danger (4,5) [(3,4),(4,6)] ==> True
|
|
-- danger (5,3) [(3,4),(4,6)] ==> False
|
|
-- danger (5,3) [(3,4),(4,6),(7,5),(6,2),(8,1)] ==> True
|
|
--
|
|
-- Hint: Use the relations of the previous exercise!
|
|
--
|
|
-- Lists of coordinates of queens will be later used in a Last In
|
|
-- First Out (LIFO) manner, so we give this type the alias Stack:
|
|
-- https://en.wikipedia.org/wiki/Stack_(abstract_data_type)
|
|
|
|
type Candidate = Coord
|
|
type Stack = [Coord]
|
|
|
|
danger :: Candidate -> Stack -> Bool
|
|
danger = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 5: In this exercise, the task is to write a modified version of
|
|
-- prettyPrint that marks those empty squares with '#' that are in the
|
|
-- collective danger zone of the given stack of queens. You may assume that
|
|
-- none of the queens in the stack are in the danger zone of another queen.
|
|
--
|
|
-- Examples:
|
|
-- *Set9b> putStrLn $ prettyPrint2 3 []
|
|
-- ...
|
|
-- ...
|
|
-- ...
|
|
--
|
|
-- *Set9b> putStrLn $ prettyPrint2 4 [(1,2),(2,4)]
|
|
-- #Q##
|
|
-- ###Q
|
|
-- .###
|
|
-- .#.#
|
|
--
|
|
-- *Set9b> putStrLn $ prettyPrint2 9 [(5,5)]
|
|
-- #...#...#
|
|
-- .#..#..#.
|
|
-- ..#.#.#..
|
|
-- ...###...
|
|
-- ####Q####
|
|
-- ...###...
|
|
-- ..#.#.#..
|
|
-- .#..#..#.
|
|
-- #...#...#
|
|
--
|
|
-- (For those that did the challenge in exercise 2, there's probably no O(n^2)
|
|
-- solution to this version. Any working solution is okay in this exercise.)
|
|
|
|
prettyPrint2 :: Size -> Stack -> String
|
|
prettyPrint2 = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 6: Now that we can check if a piece can be safely placed into a square in
|
|
-- the chessboard, it's time to write the first piece of the actual solution.
|
|
--
|
|
-- Given the size of the chessboard and a stack, the function fixFirst
|
|
-- should take the queen on the top of the stack, and if it is in
|
|
-- danger, move it right _along the same row_ (in the direction of
|
|
-- increasing columns) until it is not in danger.
|
|
--
|
|
-- If no safe spot is found for the queen on that row, fixFirst should
|
|
-- return Nothing.
|
|
--
|
|
-- Note: this means in particular that if the queen is already outside
|
|
-- the board, Nothing should be returned.
|
|
--
|
|
-- Examples:
|
|
-- fixFirst 5 [(1,1)] ==> Just [(1,1)]
|
|
-- fixFirst 5 [(3,4)] ==> Just [(3,4)]
|
|
-- fixFirst 5 [(1,1),(1,5)] ==> Nothing
|
|
-- fixFirst 5 [(1,6)] ==> Nothing
|
|
-- fixFirst 5 [(1,1),(3,3)] ==> Just [(1,2),(3,3)]
|
|
-- fixFirst 5 [(1,3),(3,3)] ==> Just [(1,4),(3,3)]
|
|
-- fixFirst 5 [(2,1),(3,3)] ==> Just [(2,1),(3,3)]
|
|
-- fixFirst 8 [(8,1),(1,1)] ==> Just [(8,2),(1,1)]
|
|
-- fixFirst 8 [(4,1),(3,4),(4,6)] ==> Nothing
|
|
-- fixFirst 8 [(6,1),(3,4),(4,6)] ==> Just [(6,2),(3,4),(4,6)]
|
|
-- fixFirst 8 [(5,1),(3,8),(4,6),(7,5),(6,2),(8,1)] ==> Nothing
|
|
--
|
|
-- Hint: Remember prettyPrint and prettyPrint2? They might be useful
|
|
-- for debugging. For example we can run this to see what's happening
|
|
-- in that last example. The whole fifth row is in danger zone.
|
|
--
|
|
-- putStrLn $ prettyPrint2 8 [(3,8),(4,6),(7,5),(6,2),(8,1)]
|
|
-- ###.####
|
|
-- ##.#####
|
|
-- #######Q
|
|
-- #####Q##
|
|
-- ########
|
|
-- #Q######
|
|
-- ####Q###
|
|
-- Q#######
|
|
|
|
fixFirst :: Size -> Stack -> Maybe Stack
|
|
fixFirst n s = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 7: We need two helper functions for stack management.
|
|
--
|
|
-- * continue moves on to a new row. It pushes a new candidate to the
|
|
-- top of the stack (front of the list). The new candidate should be
|
|
-- at the beginning of the next row with respect to the queen
|
|
-- previously on top of the stack.
|
|
--
|
|
-- * backtrack moves back to the previous row. It removes the top
|
|
-- element of the stack, and adjusts the new top element so that it
|
|
-- is in the next column.
|
|
--
|
|
-- Examples:
|
|
-- continue [(1,1)] ==> [(2,1),(1,1)]
|
|
-- continue [(2,3),(1,1)] ==> [(3,1),(2,3),(1,1)]
|
|
-- backtrack [(8,1),(7,5),(6,2),(4,6),(3,4)] ==> [(7,6),(6,2),(4,6),(3,4)]
|
|
--
|
|
-- Hint: Remember nextRow and nextCol? Use them!
|
|
|
|
continue :: Stack -> Stack
|
|
continue s = todo
|
|
|
|
backtrack :: Stack -> Stack
|
|
backtrack s = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 8: Let's take a step. Our algorithm solves the problem (in a
|
|
-- greedy manner) one row at a time, backtracking when needed. The
|
|
-- reason why we need backtracking is the following. We can greedily
|
|
-- put the queens to (1,1) and (2,3) and end up with no safe spot on
|
|
-- the third row:
|
|
--
|
|
-- Q###
|
|
-- ##Q#
|
|
-- ####
|
|
-- #.##
|
|
--
|
|
-- However if we backtrack and move the queen from (2,3) to (2,4), we
|
|
-- are able to place the third queen:
|
|
--
|
|
-- Q###
|
|
-- ###Q
|
|
-- #.##
|
|
-- ##.#
|
|
--
|
|
-- Implement the function step that takes the size of a board and a
|
|
-- stack, and tries to fix the position of the queen on the top of the
|
|
-- stack (using fixFirst). If a new position is found, the function
|
|
-- should call continue to return a stack with a new candidate. If a
|
|
-- safe position is not found, the function should call backtrack to
|
|
-- return a new stack.
|
|
--
|
|
-- Examples:
|
|
--
|
|
-- The first candidate is safe so we continue directly:
|
|
-- step 4 [(1,1)] ==> [(2,1),(1,1)]
|
|
--
|
|
-- Q... Q...
|
|
-- .... ==> Q...
|
|
-- .... ....
|
|
-- .... ....
|
|
--
|
|
-- The second candidate needs to be adjusted a bit before a third is added:
|
|
-- step 4 [(2,1),(1,1)] ==> [(3,1),(2,3),(1,1)]
|
|
--
|
|
-- Q... Q...
|
|
-- Q... ==> ..Q.
|
|
-- .... Q...
|
|
-- .... ....
|
|
--
|
|
-- No safe position is found for the third queen so we backtrack:
|
|
-- step 4 [(3,1),(2,3),(1,1)] ==> [(2,4),(1,1)]
|
|
--
|
|
-- Q... Q...
|
|
-- ..Q. ==> ...Q
|
|
-- Q... ....
|
|
-- .... ....
|
|
--
|
|
-- The new position of the second queen is ok so we move to the third row:
|
|
-- step 4 [(2,4),(1,1)] ==> [(3,1),(2,4),(1,1)]
|
|
--
|
|
-- Q... Q...
|
|
-- ...Q ==> ...Q
|
|
-- .... Q...
|
|
-- .... ....
|
|
--
|
|
-- More examples:
|
|
-- step 8 [(4,2),(3,5),(2,3),(1,1)] ==> [(5,1),(4,2),(3,5),(2,3),(1,1)]
|
|
-- step 8 [(5,1),(4,2),(3,5),(2,3),(1,1)] ==> [(6,1),(5,4),(4,2),(3,5),(2,3),(1,1)]
|
|
-- step 8 [(6,1),(5,4),(4,2),(3,5),(2,3),(1,1)] ==> [(5,5),(4,2),(3,5),(2,3),(1,1)]
|
|
|
|
step :: Size -> Stack -> Stack
|
|
step = todo
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Ex 9: Let's solve our puzzle! The function finish takes a partial
|
|
-- solution (stack) and repeatedly step until a complete solution is
|
|
-- found.
|
|
--
|
|
-- Reminder: a complete solution has n queens that don't threaten each
|
|
-- other. One easy way to know you have a valid solution is when step
|
|
-- adds the (n+1)th queen.
|
|
--
|
|
-- After this, it's just a matter of calling `finish n [(1,1)]` to
|
|
-- solve the n queens problem.
|
|
|
|
finish :: Size -> Stack -> Stack
|
|
finish = todo
|
|
|
|
solve :: Size -> Stack
|
|
solve n = finish n [(1,1)]
|