def simple_reduction(puzzle): """ simple_reduction returns a solution to <puzzle>. It works by reducing the number of greenhouses one by one until it has the lowest cost and meets the max constraint. """ max, field = puzzle # figure out the current number of greenhouses greenhouses = common.ids(field) # we need to keep a copy of the previous field and it's cost in order # to return it once we've realized we've done one reduction too many prev_field, prev_cost = None, sys.maxint if len(greenhouses) <= max: prev_field, prev_cost = copy.deepcopy(field), common.cost(field) # join greenhouses until when run out of them or until max constraint # is met *and* cost increases from one reduction to the next while len(greenhouses) > 1: j1, j2, js = 0, 0, sys.maxint # try each combination of greenhouses for g1, g2 in itertools.combinations(greenhouses, 2): # find outer bounds (left, right, top and bottom) for a greenhouse made # up of g1 and g2 size3, p31, p32 = common.outer_bounds([g1, g2], field) if size3 is not None: size1, p11, p12 = common.outer_bounds(g1, field) size2, p21, p22 = common.outer_bounds(g2, field) diff = size3 - size2 - size1 if diff < js: j1, j2, js = g1, g2, diff # if we run out of combinations to try # we must either surrender (return None) # or if len(greenhouses) <= max return # the best solution we have. if j1 == 0: if len(greenhouses) <= max: return max, prev_field else: return max, None # join j1 and j2, remove j2 from greenhouses field = common.join(j1, j2, field) greenhouses.remove(j2) # decide if we should exit this loop or keep on reducing curr_cost = common.cost(field) if len(greenhouses) < max: if prev_cost < curr_cost: return max, prev_field prev_field, prev_cost = copy.deepcopy(field), curr_cost # if we end up here, we've come down to 1 greenhouse return max, field
def search(puzzle, breakpoint = 2): """ search produces a solution to <puzzle>. >>> solve("p3.text") # doctest: +ELLIPSIS 71 ... """ max, field = puzzle solution = (common.cost(field), field) paths = [solution, ] while len(paths) > 0: curr_cost, field = paths.pop(0) # Figure out the current number of greenhouses greenhouses = common.ids(field) if len(greenhouses) > 1: diffs = {} # Try each combination of greenhouses for g1, g2 in itertools.combinations(greenhouses, 2): # Find outer bounds (left, right, top and bottom) for a greenhouse made # up of g1 and g2 size3, p31, p32 = common.outer_bounds([g1, g2], field) if size3 is not None: size1, p11, p12 = common.outer_bounds(g1, field) size2, p21, p22 = common.outer_bounds(g2, field) diff = size3 - size2 - size1 if diff not in diffs.keys(): diffs[diff] = [(g1, g2),] else: diffs[diff].append((g1, g2)) # Find the list of joins which has the lowest diff and select the joins # of the most frequent greenhouse. if len(diffs.keys()) > 0: freqs = {} for (g1, g2) in diffs[sorted(diffs.keys())[0]]: if g1 not in freqs.keys(): freqs[g1] = [(g1, g2),] else: freqs[g1].append((g1, g2)) if g2 not in freqs.keys(): freqs[g2] = [(g1, g2),] else: freqs[g2].append((g1, g2)) # Perform each join in a fresh copy of field and add it to paths if # cost is lower than current cost, otherwise compare cost to solution # and either discard this path or add it as best-so-far. joins = freqs[sorted(freqs.keys(), key = lambda k: len(freqs[k]), reverse = True)[0]] if len(joins) <= breakpoint: (g1, g2) = joins[0] _, _field = s1.simple_reduction((max, common.join(g1, g2, copy.deepcopy(field)))) cf = common.cost(_field) if cf < solution[0] and \ len(common.ids(_field)) <= max: solution = (cf, _field) else: for (g1, g2) in joins: _field = common.join(g1, g2, copy.deepcopy(field)) cf = common.cost(_field) if cf < curr_cost: paths.append((cf, _field)) if cf < solution[0] and \ len(common.ids(_field)) <= max: solution = (cf, _field) return max, solution[1]