-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAttempt4.hs
More file actions
175 lines (155 loc) · 6.82 KB
/
Copy pathAttempt4.hs
File metadata and controls
175 lines (155 loc) · 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
module Attempt4
( solution,
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Logic
import Data.Function (on)
import Data.List (minimumBy)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (isJust)
import Data.Set (Set)
import qualified Data.Set as Set
import Problem
data Attempt = HasQueen | Eliminated
deriving (Show, Eq)
data Remaining a = Satisfied | AvailableCandidates (Set a)
deriving (Show, Eq)
remove :: (MonadLogic m, Ord k, Ord a) => k -> a -> Map k (Remaining a) -> m (Map k (Remaining a))
remove key a candidates = do
case Map.lookup key candidates of
Just (AvailableCandidates s) -> do
-- Note: the condition is (|s| > 1)
-- we do not want to be left with 0 candidates
guard (Set.size s > 1)
let newSet = Set.delete a s
pure $ Map.insert key (AvailableCandidates newSet) candidates
_ -> pure candidates
-- | Data structure representing our progress in solving the problem
data Partial = Partial
{ -- | Whether each square has a queen, or has been eliminated
attempts :: Map (Row, Column) Attempt,
-- | For each row i, whether it already has a queen,
-- | or a set of columns j, such that placing a queen
-- | in (i, j) is still possible
rowCandidates :: Map Row (Remaining Column),
-- | Candidates (i, j) for each column j, or
-- | whether the column already has a queen
columnCandidates :: Map Column (Remaining Row),
-- | Candidates (i, j) for each color, or
-- | whether the colored region already has a queen
colorCandidates :: Map Color (Remaining (Row, Column))
}
deriving (Show, Eq)
-- | Mark a cell in the board as eliminated, and update the candidates accordingly
-- | If the cell already has a queen, do nothing
eliminate :: (MonadLogic m) => (Row, Column) -> Problem -> Partial -> m Partial
eliminate (x, y) problem partial
| isJust currentCellValue = pure partial
| otherwise = do
let newAttempts = Map.insert (x, y) Eliminated partial.attempts
newRowCandidates <- remove x y partial.rowCandidates
newColumnCandidates <- remove y x partial.columnCandidates
newColorCandidates <- remove color (x, y) partial.colorCandidates
pure $
Partial
{ attempts = newAttempts,
rowCandidates = newRowCandidates,
columnCandidates = newColumnCandidates,
colorCandidates = newColorCandidates
}
where
color = problem ! (x, y)
currentCellValue = Map.lookup (x, y) partial.attempts
elimCorners :: (MonadLogic m) => (Row, Column) -> Problem -> Partial -> m Partial
elimCorners (x, y) problem = foldr (>=>) pure elimFns
where
elimFns =
[ eliminate (x', y') problem
| x' <- [x - 1, x + 1],
x' >= 0,
x' < size problem,
y' <- [y - 1, y + 1],
y' >= 0,
y' < size problem
]
elimColumn :: (MonadLogic m) => Column -> Problem -> Partial -> m Partial
elimColumn j problem partial = foldM (flip elimCell) partial [0 .. size problem - 1]
where
elimCell i = eliminate (i, j) problem
elimRow :: (MonadLogic m) => Row -> Problem -> Partial -> m Partial
elimRow i problem partial = foldM (flip elimCell) partial [0 .. size problem - 1]
where
elimCell j = eliminate (i, j) problem
elimColor :: (MonadLogic m) => Color -> Problem -> Partial -> m Partial
elimColor color problem = foldr (>=>) pure elimFns
where
elimFns = [eliminate (x, y) problem | x <- [0 .. size problem - 1], y <- [0 .. size problem - 1], problem ! (x, y) == color]
-- | Place a queen in the given cell, and update the partial progress
-- | This eiliminates a number of other candidates sharing the same
-- | row, column, or color, and those which are in the corners of the cell
placeQueen :: (MonadLogic m) => Problem -> (Row, Column) -> Partial -> m Partial
placeQueen problem (x, y) partial = elimAll newPartial
where
newAttempts = Map.insert (x, y) HasQueen partial.attempts
newRowCandidates = Map.insert x Satisfied partial.rowCandidates
newColumnCandidates = Map.insert y Satisfied partial.columnCandidates
color = problem ! (x, y)
newColorCandidates = Map.insert color Satisfied partial.colorCandidates
newPartial =
Partial
{ attempts = newAttempts,
rowCandidates = newRowCandidates,
columnCandidates = newColumnCandidates,
colorCandidates = newColorCandidates
}
elimAll =
elimCorners (x, y) problem
>=> elimColumn y problem
>=> elimRow x problem
>=> elimColor color problem
-- | A strategy is a set of (Row, Column) candidates such that at exactly one
-- | of them must be included in the completion of the solution
type Strategy = Set (Row, Column)
strategies :: Partial -> [Strategy]
strategies partial =
[Set.fromList [(r, c) | c <- Set.toList s] | (r, AvailableCandidates s) <- Map.toList partial.rowCandidates]
++ [Set.fromList [(r, c) | r <- Set.toList s] | (c, AvailableCandidates s) <- Map.toList partial.columnCandidates]
++ [Set.fromList [(i, j) | (i, j) <- Set.toList s] | (_, AvailableCandidates s) <- Map.toList partial.colorCandidates]
choose :: (MonadLogic m, Foldable t) => t a -> m a
choose = foldr ((<|>) . pure) empty
candidate :: (MonadLogic m) => Partial -> m (Row, Column)
candidate partial
| null availableStrategies = empty
| otherwise = choose bestStrategy
where
availableStrategies = strategies partial
-- We choose the strategy with the least number of candidates
bestStrategy = minimumBy (compare `on` Set.size) availableStrategies
-- do an action n times
repeatM :: (Monad m) => Int -> (a -> m a) -> a -> m a
repeatM n f = foldr (>=>) pure (replicate n f)
extend :: (MonadLogic m) => Problem -> Partial -> m Partial
extend problem partial = do
pos <- candidate partial
-- place a queen on the candidate cell
placeQueen problem pos partial
queenView :: Partial -> [(Row, Column)]
queenView partial = [(x, y) | ((x, y), status) <- Map.toList partial.attempts, status == HasQueen]
solution :: (MonadLogic m) => Problem -> m [(Row, Column)]
solution problem = do
endState <- repeatM (size problem) (extend problem) (mkPartial problem)
pure $ queenView endState
-- -- | Create an initial empty partial solution for the given problem
mkPartial :: Problem -> Partial
mkPartial problem =
Partial
{ attempts = Map.empty,
rowCandidates = Map.fromList [(r, AvailableCandidates (Set.fromList [0 .. size problem - 1])) | r <- [0 .. size problem - 1]],
columnCandidates = Map.fromList [(c, AvailableCandidates (Set.fromList [0 .. size problem - 1])) | c <- [0 .. size problem - 1]],
colorCandidates = Map.fromList [(color, AvailableCandidates (colorCandidates color)) | color <- [0 .. size problem - 1]]
}
where
colorCandidates color = Set.fromList [(i, j) | i <- [0 .. size problem - 1], j <- [0 .. size problem - 1], problem ! (i, j) == color]