33from utils import argmin_random_tie , count , first
44import search
55
6- from collections import defaultdict
6+ from collections import defaultdict , Counter
77from functools import reduce
88
99import itertools
@@ -50,13 +50,12 @@ class CSP(search.Problem):
5050
5151 def __init__ (self , variables , domains , neighbors , constraints ):
5252 """Construct a CSP problem. If variables is empty, it becomes domains.keys()."""
53+ super ().__init__ (())
5354 variables = variables or list (domains .keys ())
54-
5555 self .variables = variables
5656 self .domains = domains
5757 self .neighbors = neighbors
5858 self .constraints = constraints
59- self .initial = ()
6059 self .curr_domains = None
6160 self .nassigns = 0
6261
@@ -74,10 +73,12 @@ def unassign(self, var, assignment):
7473
7574 def nconflicts (self , var , val , assignment ):
7675 """Return the number of conflicts var=val has with other variables."""
76+
7777 # Subclasses may implement this more efficiently
7878 def conflict (var2 ):
7979 return (var2 in assignment and
8080 not self .constraints (var , val , var2 , assignment [var2 ]))
81+
8182 return count (conflict (v ) for v in self .neighbors [var ])
8283
8384 def display (self , assignment ):
@@ -153,6 +154,7 @@ def conflicted_vars(self, current):
153154 return [var for var in self .variables
154155 if self .nconflicts (var , current [var ], current ) > 0 ]
155156
157+
156158# ______________________________________________________________________________
157159# Constraint Propagation with AC-3
158160
@@ -183,6 +185,51 @@ def revise(csp, Xi, Xj, removals):
183185 revised = True
184186 return revised
185187
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+
186233# ______________________________________________________________________________
187234# CSP Backtracking Search
188235
@@ -208,6 +255,7 @@ def num_legal_values(csp, var, assignment):
208255 return count (csp .nconflicts (var , val , assignment ) == 0
209256 for val in csp .domains [var ])
210257
258+
211259# Value ordering
212260
213261
@@ -221,6 +269,7 @@ def lcv(var, assignment, csp):
221269 return sorted (csp .choices (var ),
222270 key = lambda val : csp .nconflicts (var , val , assignment ))
223271
272+
224273# Inference
225274
226275
@@ -245,6 +294,7 @@ def mac(csp, var, value, assignment, removals):
245294 """Maintain arc consistency."""
246295 return AC3 (csp , {(X , var ) for X in csp .neighbors [var ]}, removals )
247296
297+
248298# The search, proper
249299
250300
@@ -274,6 +324,7 @@ def backtrack(assignment):
274324 assert result is None or csp .goal_test (result )
275325 return result
276326
327+
277328# ______________________________________________________________________________
278329# Min-conflicts hillclimbing search for CSPs
279330
@@ -302,6 +353,7 @@ def min_conflicts_value(csp, var, current):
302353 return argmin_random_tie (csp .domains [var ],
303354 key = lambda val : csp .nconflicts (var , val , current ))
304355
356+
305357# ______________________________________________________________________________
306358
307359
@@ -356,7 +408,7 @@ def build_topological(node, parent, neighbors, visited, stack, parents):
356408 visited [node ] = True
357409
358410 for n in neighbors [node ]:
359- if (not visited [n ]):
411+ if (not visited [n ]):
360412 build_topological (n , node , neighbors , visited , stack , parents )
361413
362414 parents [node ] = parent
@@ -366,9 +418,9 @@ def build_topological(node, parent, neighbors, visited, stack, parents):
366418def make_arc_consistent (Xj , Xk , csp ):
367419 """Make arc between parent (Xj) and child (Xk) consistent under the csp's constraints,
368420 by removing the possible values of Xj that cause inconsistencies."""
369- #csp.curr_domains[Xj] = []
421+ # csp.curr_domains[Xj] = []
370422 for val1 in csp .domains [Xj ]:
371- keep = False # Keep or remove val1
423+ keep = False # Keep or remove val1
372424 for val2 in csp .domains [Xk ]:
373425 if csp .constraints (Xj , val1 , Xk , val2 ):
374426 # Found a consistent assignment for val1, keep it
@@ -393,6 +445,7 @@ def assign_value(Xj, Xk, csp, assignment):
393445 # No consistent assignment available
394446 return None
395447
448+
396449# ______________________________________________________________________________
397450# Map-Coloring Problems
398451
@@ -468,6 +521,7 @@ def parse_neighbors(neighbors, variables=None):
468521 PI; PA: LR RA; PC: PL CE LI AQ; PI: NH NO CA IF; PL: BR NB CE PC; RA:
469522 AU BO FC PA LR""" )
470523
524+
471525# ______________________________________________________________________________
472526# n-Queens Problem
473527
@@ -503,16 +557,16 @@ def __init__(self, n):
503557 CSP .__init__ (self , list (range (n )), UniversalDict (list (range (n ))),
504558 UniversalDict (list (range (n ))), queen_constraint )
505559
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 )
509563
510564 def nconflicts (self , var , val , assignment ):
511565 """The number of conflicts, as recorded with each assignment.
512566 Count conflicts in row and in up, down diagonals. If there
513567 is a queen there, it can't conflict with itself, so subtract 3."""
514568 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 ]
516570 if assignment .get (var , None ) == val :
517571 c -= 3
518572 return c
@@ -560,6 +614,7 @@ def display(self, assignment):
560614 print (str (self .nconflicts (var , val , assignment )) + ch , end = ' ' )
561615 print ()
562616
617+
563618# ______________________________________________________________________________
564619# Sudoku
565620
@@ -646,9 +701,12 @@ def show_cell(cell): return str(assignment.get(cell, '.'))
646701
647702 def abut (lines1 , lines2 ): return list (
648703 map (' | ' .join , list (zip (lines1 , lines2 ))))
704+
649705 print ('\n ------+-------+------\n ' .join (
650706 '\n ' .join (reduce (
651707 abut , map (show_box , brow ))) for brow in self .bgrid ))
708+
709+
652710# ______________________________________________________________________________
653711# The Zebra Puzzle
654712
@@ -716,6 +774,7 @@ def zebra_constraint(A, a, B, b, recurse=0):
716774 (A in Smokes and B in Smokes )):
717775 return not same
718776 raise Exception ('error' )
777+
719778 return CSP (variables , domains , neighbors , zebra_constraint )
720779
721780
0 commit comments