3
3
from utils import argmin_random_tie , count , first
4
4
import search
5
5
6
- from collections import defaultdict
6
+ from collections import defaultdict , Counter
7
7
from functools import reduce
8
8
9
9
import itertools
@@ -50,13 +50,12 @@ class CSP(search.Problem):
50
50
51
51
def __init__ (self , variables , domains , neighbors , constraints ):
52
52
"""Construct a CSP problem. If variables is empty, it becomes domains.keys()."""
53
+ super ().__init__ (())
53
54
variables = variables or list (domains .keys ())
54
-
55
55
self .variables = variables
56
56
self .domains = domains
57
57
self .neighbors = neighbors
58
58
self .constraints = constraints
59
- self .initial = ()
60
59
self .curr_domains = None
61
60
self .nassigns = 0
62
61
@@ -74,10 +73,12 @@ def unassign(self, var, assignment):
74
73
75
74
def nconflicts (self , var , val , assignment ):
76
75
"""Return the number of conflicts var=val has with other variables."""
76
+
77
77
# Subclasses may implement this more efficiently
78
78
def conflict (var2 ):
79
79
return (var2 in assignment and
80
80
not self .constraints (var , val , var2 , assignment [var2 ]))
81
+
81
82
return count (conflict (v ) for v in self .neighbors [var ])
82
83
83
84
def display (self , assignment ):
@@ -153,6 +154,7 @@ def conflicted_vars(self, current):
153
154
return [var for var in self .variables
154
155
if self .nconflicts (var , current [var ], current ) > 0 ]
155
156
157
+
156
158
# ______________________________________________________________________________
157
159
# Constraint Propagation with AC-3
158
160
@@ -183,6 +185,51 @@ def revise(csp, Xi, Xj, removals):
183
185
revised = True
184
186
return revised
185
187
188
+
189
+ # Constraint Propagation with AC-4
190
+
191
+ def AC4 (csp , queue = None , removals = None ):
192
+ """AC4 algorithm runs in O(cd^2) worst-case time but can be slower
193
+ than AC3 on average cases"""
194
+ if queue is None :
195
+ queue = {(Xi , Xk ) for Xi in csp .variables for Xk in csp .neighbors [Xi ]}
196
+ csp .support_pruning ()
197
+ support_counter = Counter ()
198
+ variable_value_pairs_supported = defaultdict (set )
199
+ unsupported_variable_value_pairs = []
200
+ # construction and initialization of support sets
201
+ while queue :
202
+ (Xi , Xj ) = queue .pop ()
203
+ revised = False
204
+ for x in csp .curr_domains [Xi ][:]:
205
+ for y in csp .curr_domains [Xj ]:
206
+ if csp .constraints (Xi , x , Xj , y ):
207
+ support_counter [(Xi , x , Xj )] += 1
208
+ variable_value_pairs_supported [(Xj , y )].add ((Xi , x ))
209
+ if support_counter [(Xi , x , Xj )] == 0 :
210
+ csp .prune (Xi , x , removals )
211
+ revised = True
212
+ unsupported_variable_value_pairs .append ((Xi , x ))
213
+ if revised :
214
+ if not csp .curr_domains [Xi ]:
215
+ return False
216
+ # propagation of removed values
217
+ while unsupported_variable_value_pairs :
218
+ Xj , y = unsupported_variable_value_pairs .pop ()
219
+ for Xi , x in variable_value_pairs_supported [(Xj , y )]:
220
+ revised = False
221
+ if x in csp .curr_domains [Xi ][:]:
222
+ support_counter [(Xi , x , Xj )] -= 1
223
+ if support_counter [(Xi , x , Xj )] == 0 :
224
+ csp .prune (Xi , x , removals )
225
+ revised = True
226
+ unsupported_variable_value_pairs .append ((Xi , x ))
227
+ if revised :
228
+ if not csp .curr_domains [Xi ]:
229
+ return False
230
+ return True
231
+
232
+
186
233
# ______________________________________________________________________________
187
234
# CSP Backtracking Search
188
235
@@ -208,6 +255,7 @@ def num_legal_values(csp, var, assignment):
208
255
return count (csp .nconflicts (var , val , assignment ) == 0
209
256
for val in csp .domains [var ])
210
257
258
+
211
259
# Value ordering
212
260
213
261
@@ -221,6 +269,7 @@ def lcv(var, assignment, csp):
221
269
return sorted (csp .choices (var ),
222
270
key = lambda val : csp .nconflicts (var , val , assignment ))
223
271
272
+
224
273
# Inference
225
274
226
275
@@ -245,6 +294,7 @@ def mac(csp, var, value, assignment, removals):
245
294
"""Maintain arc consistency."""
246
295
return AC3 (csp , {(X , var ) for X in csp .neighbors [var ]}, removals )
247
296
297
+
248
298
# The search, proper
249
299
250
300
@@ -274,6 +324,7 @@ def backtrack(assignment):
274
324
assert result is None or csp .goal_test (result )
275
325
return result
276
326
327
+
277
328
# ______________________________________________________________________________
278
329
# Min-conflicts hillclimbing search for CSPs
279
330
@@ -302,6 +353,7 @@ def min_conflicts_value(csp, var, current):
302
353
return argmin_random_tie (csp .domains [var ],
303
354
key = lambda val : csp .nconflicts (var , val , current ))
304
355
356
+
305
357
# ______________________________________________________________________________
306
358
307
359
@@ -356,7 +408,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents):
356
408
visited [node ] = True
357
409
358
410
for n in neighbors [node ]:
359
- if (not visited [n ]):
411
+ if (not visited [n ]):
360
412
build_topological (n , node , neighbors , visited , stack , parents )
361
413
362
414
parents [node ] = parent
@@ -366,9 +418,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents):
366
418
def make_arc_consistent (Xj , Xk , csp ):
367
419
"""Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints,
368
420
by removing the possible values of Xj that cause inconsistencies."""
369
- #csp.curr_domains[Xj] = []
421
+ # csp.curr_domains[Xj] = []
370
422
for val1 in csp .domains [Xj ]:
371
- keep = False # Keep or remove val1
423
+ keep = False # Keep or remove val1
372
424
for val2 in csp .domains [Xk ]:
373
425
if csp .constraints (Xj , val1 , Xk , val2 ):
374
426
# Found a consistent assignment for val1, keep it
@@ -393,6 +445,7 @@ def assign_value(Xj, Xk, csp, assignment):
393
445
# No consistent assignment available
394
446
return None
395
447
448
+
396
449
# ______________________________________________________________________________
397
450
# Map-Coloring Problems
398
451
@@ -468,6 +521,7 @@ def parse_neighbors(neighbors, variables=None):
468
521
PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA:
469
522
AU BO FC PA LR""" )
470
523
524
+
471
525
# ______________________________________________________________________________
472
526
# n-Queens Problem
473
527
@@ -503,16 +557,16 @@ def __init__(self, n):
503
557
CSP .__init__ (self , list (range (n )), UniversalDict (list (range (n ))),
504
558
UniversalDict (list (range (n ))), queen_constraint )
505
559
506
- self .rows = [0 ]* n
507
- self .ups = [0 ]* ( 2 * n - 1 )
508
- self .downs = [0 ]* ( 2 * n - 1 )
560
+ self .rows = [0 ] * n
561
+ self .ups = [0 ] * ( 2 * n - 1 )
562
+ self .downs = [0 ] * ( 2 * n - 1 )
509
563
510
564
def nconflicts (self , var , val , assignment ):
511
565
"""The number of conflicts, as recorded with each assignment.
512
566
Count conflicts in row and in up, down diagonals. If there
513
567
is a queen there, it can't conflict with itself, so subtract 3."""
514
568
n = len (self .variables )
515
- c = self .rows [val ] + self .downs [var + val ] + self .ups [var - val + n - 1 ]
569
+ c = self .rows [val ] + self .downs [var + val ] + self .ups [var - val + n - 1 ]
516
570
if assignment .get (var , None ) == val :
517
571
c -= 3
518
572
return c
@@ -560,6 +614,7 @@ def display(self, assignment):
560
614
print (str (self .nconflicts (var , val , assignment )) + ch , end = ' ' )
561
615
print ()
562
616
617
+
563
618
# ______________________________________________________________________________
564
619
# Sudoku
565
620
@@ -646,9 +701,12 @@ def show_cell(cell): return str(assignment.get(cell, '.'))
646
701
647
702
def abut (lines1 , lines2 ): return list (
648
703
map (' | ' .join , list (zip (lines1 , lines2 ))))
704
+
649
705
print ('\n ------+-------+------\n ' .join (
650
706
'\n ' .join (reduce (
651
707
abut , map (show_box , brow ))) for brow in self .bgrid ))
708
+
709
+
652
710
# ______________________________________________________________________________
653
711
# The Zebra Puzzle
654
712
@@ -716,6 +774,7 @@ def zebra_constraint(A, a, B, b, recurse=0):
716
774
(A in Smokes and B in Smokes )):
717
775
return not same
718
776
raise Exception ('error' )
777
+
719
778
return CSP (variables , domains , neighbors , zebra_constraint )
720
779
721
780
0 commit comments