使用修改版Dancing link X (舞蹈链)求解aquarium游戏

  如果把舞蹈表的所有行的消除条件,改成覆盖总值达到n后消除,而不是覆盖总值达到1后消除,并且覆盖的行值也不是1,那会怎么样?

  就变成了多值覆盖游戏!(其实这不就是舞蹈链的重复覆盖特殊情况了嘛)

  正好这里有个游戏要解:https://www.puzzle-aquarium.com/里面的aquarium游戏,规则是:

1、游戏棋盘被分成几块,每块被称为一个“水箱”
2、游戏中你可以给每个水箱灌一些水,也可以让它空着
3、同一个水箱内的水位是等高的。也即同一水箱内的同一行的单元格,要么都有水,要么都空着。
4、棋盘外面的数字是该行或该列,灌水的单元格总数。

  说白了,每行每列所占的方格数必须正好等于右侧上面标明的限制数;同水箱内所有最高方格互相之间必须等高;同水箱内最高方格与最低方格内的所有方格必须全部填满。

  如下所示:

图1.ID为69,467的谜面答案示例

  这里同水箱有固定不同的方格排布,可以事先把不同高度的排布在横竖方向上的占有数量记录下来;像上图左下角有个水箱,对应到排布就是:

  0分布;第9行有5个,第0列有1个,第1列有1个,第2列有1个,第3列有1个,第4列有1个;第8行有1个,第9行有5个,第0列有1个,第1列有2个,第2列有1个,第3列有1个,第4列有1个。

  舞蹈表的列可以这样设计:不同的水箱占据;不同行的横方向方格占据数量;不同列的竖方向方格占据数量。这样就可以达成上述的解题思路。为了舞蹈表出解时能判读对应排布,这里把行的id和水箱ID、水高度对应起来。

  主要的解决代码:

"""
dominosa solver using Dancing Links.
"""
from functools import reduce
from dlmatrix import DancingLinksMatrix
from alg_x import AlgorithmX
import math
import time

__author__ = 'Funcfans'
chess = []
limits = []
presum = []
occupy = []
heights = []
currheights = []
heightIdxs = []
rowsize = 0
maxnum = 0
def insertToStr(origin, i, j, value):
    if(origin[i][j] != '+'):
        origin[i] =  origin[i][:j] + value + origin[i][j+1:]

def get_names(maxnum):
    cnt = 0
    for i in range(maxnum):
        yield f'B({i})' # block
        cnt += 1
    #
    base = maxnum
    presum.append([])
    for i,limit in enumerate(limits[0]):
        if limit == 0:continue
        presum[0].append(base)
        yield (f'H({i},{limit})', limit)
        base += 1
    #
    presum.append([])
    for i,limit in enumerate(limits[1]):
        if limit == 0:continue
        presum[1].append(base)
        yield (f'V({i},{limit})', limit)
        base += 1
#
def compute_row(value, maxnum):
    row = []
    row.append(value)
    for d in range(2):
        for i,li in enumerate(occupy[d][value]):
            if li == 0:continue
            if li > limits[d][i]:return None
            row.append((presum[d][i],li))
    return row
#
class PrintFirstSol:
    def __init__(self, r, c):
        self.r = r
        self.c = c

    def __call__(self, sol):
        subSubImg = ' ' * rowsize
        solImg = reduce(lambda a,b:a+'
'+b,[subSubImg] * rowsize)
        blockheight = [0] * maxnum
        startHeight = {}
        for v in sol.keys():
            #v.sort()
            blockheight[heights[v][0]] = heights[v][1]
            #
        #
        for i in range(rowsize)[::-1]:
            for j in range(rowsize):
                if chess[i][j] not in startHeight:
                    startHeight[chess[i][j]] = i
                if startHeight[chess[i][j]] - blockheight[chess[i][j]] + 1 > i:
                    chess[i][j] = -1
        subSubImg = '-'.join(['+'] * (rowsize+1))
        for i in range(rowsize):
            print(subSubImg)
            print('|',end='')
            for j in range(rowsize):
                if chess[i][j] == -1:
                    print(' |',end='')
                else:
                    print('*|',end='')
            print('')
        print(subSubImg)
        return True

def main(fileName='aquarium69,467_3.txt'):
    currheights.clear()
    heights.clear()
    heightIdxs.clear()
    start = time.time()
    with open(fileName,'r') as f:
        chessStr = f.read()
    rowStrs = chessStr.split('
')
    global rowsize, maxnum
    rowsize = len(rowStrs) - 2
    colSize = len(rowStrs[0].split(' '))
    maxnum = 0
    for rowStr in rowStrs[:-2]:
        row = []
        for colStr in rowStr.split(' '):
            maxnum = maxnum if int(colStr) < maxnum else int(colStr)
            row.append(int(colStr))
        chess.append(row)
    #print(chess)
    list(map(lambda a:int(a),rowStrs[-1].split(' ')))
    limits.append(list(map(lambda a:int(a),rowStrs[-2].split(' '))))
    limits.append(list(map(lambda a:int(a),rowStrs[-1].split(' '))))
    maxnum += 1
    d = DancingLinksMatrix(get_names(maxnum))
    occupy.append([[0 for ___ in range(rowsize)] for __ in range(maxnum)])
    occupy.append([[0 for ___ in range(rowsize)] for __ in range(maxnum)])
    rowidx = 0
    for i in range(maxnum):
        row = compute_row(i, maxnum)
        #print(f'row ({i},0) :', row)
        d.add_sparse_row(row, already_sorted=True)
        currheights.append(1)
        heightIdxs.append([i])
        heights.append((i, 0))
        rowidx += 1
    for i,row in list(enumerate(chess))[::-1]:
        used = set()
        for j,col in enumerate(row):
            used.add(col)
            occupy[0][col][i] += 1
            occupy[1][col][j] += 1
        for col in used:
            row = compute_row(col, maxnum)
            if row == None or len(row) <= 0:continue
            #print(f'row ({col},{currheights[col]}) :', row)
            d.add_sparse_row(row, already_sorted=True)
            heights.append((col, currheights[col]))
            heightIdxs[col].append(rowidx)
            currheights[col] += 1
            rowidx += 1
    #print('heights :', heights)
    #print('heightIdxs :', heightIdxs)
    ansIdx=[0 ,62 ,63 ,25 ,37 ,52 ,32 ,54 ,40 ,17 ,34 ,19 ,24 ,13]
    #[0, 13, 17, 19, 24, 25, 32, 34, 37, 40, 52, 54, 62, 63]
    ansIdx.sort()
    #print(ansIdx)
    d.end_add()
    p = PrintFirstSol(rowsize, colSize)
    AlgorithmX(d, p)()
    end = time.time()
    print('the DLX runtime is : ' + str(end-start) + 's')
    

if __name__ == "__main__":
    main('aquarium2,680,806_8.txt')
View Code

  这里的解决算法有些变化,需要修改之前的模板。

  解出问题的算法:

"""
Implementation of Donald Knuth's Algorithm X
(http://arxiv.org/abs/cs/0011047).
"""

from dlmatrix import DancingLinksMatrix, iterate_cell, MatrixDisplayer
import string

__author__ = 'FunCfans'

testRow = [0 ,62 ,63 ,25 ,37 ,52 ,32 ,54 ,40 ,17 ,34 ,19 ,24 ,13]

class AlgorithmX:
    """Callable object implementing the Algorithm X."""

    def __init__(self, matrix, callback, choose_min=True):
        """
        Creates an Algorithm_X object that solves the problem
        encoded in matrix.
        :param matrix: The DL_Matrix instance.
        :param callback: The callback called on every solution. callback has to
                         be a function receiving a dict argument
                         {row_index: linked list of the row}, and can return a
                         bool value. The solver keeps going on until the
                         callback returns a True value.
        :param choose_min: If True, the column with the minimum number of 1s is
                           chosen at each iteration, if False a random column is
                           chosen.
        """
        self.sol_dict = {}
        self.stop = False
        self.matrix = matrix
        self.callback = callback
        self.choose_min = choose_min
        self.deduce_cnt = 0
        self.depth = 0
        self.last_matrix = None
        self.delta_file = None

    def __call__(self):
        """Starts the search."""
        #self.delta_file = open('delta_matrix.txt','w',encoding='utf-8')
        #self._print(self.matrix.header, 'start')
        self._search(0)
        #self.delta_file.close()

    def _print(self, currrow, op):
        self.deduce_cnt += 1
        f = open('step/' + 'step%04d_' % (self.deduce_cnt) + op + '_' + str(currrow.indexes) + '_depth%d.txt'%(self.depth),'w',encoding='utf-8')
        printrow = {}
        rowcontent = ''
        content = 'curr ' + op + ' row : ' + str(currrow.indexes) + '
'
        content = ''
        for col in iterate_cell(self.matrix.header, 'R'):
            content += 'col name : '+col.name+' col limit : ' + str(col.limit_num)
            for row in iterate_cell(col, 'D'):
                content += ' ' + str((row.indexes[0],row.limit_num)) + ','
                printrow[row.indexes[0]] = row
            content += '
'
        content += '
'
        for k,v in printrow.items():
            content += 'row %d : '%(k) + str((v.limit_num, v.indexes[1])) + '-->'
            qv = v.R
            while(qv != v):
                content += str((qv.limit_num, qv.indexes[1])) + '-->'
                qv = qv.R
            content += str((qv.limit_num, qv.indexes[1])) + '
'
        f.write(content)
        f.close()
        
        if self.last_matrix == None:
            self.last_matrix = {}
            for col in iterate_cell(self.matrix.header, 'R'):
                self.last_matrix[(col.name, col.indexes[0])] = collist = set()
                for row in iterate_cell(col, 'D'):
                    collist.add(row.indexes[0])
        else:
            curr_matrix = {}
            for col in iterate_cell(self.matrix.header, 'R'):
                curr_matrix[(col.name, col.indexes[0])] = collist = set()
                for row in iterate_cell(col, 'D'):
                    collist.add(row.indexes[0])
            add_col = set()
            addv = set(curr_matrix.keys()).difference(set(self.last_matrix.keys()))
            if len(addv) > 0:
                self.delta_file.write('add_col : '+str(addv) + ' ')
            delv = set(self.last_matrix.keys()).difference(set(curr_matrix.keys()))
            if len(delv) > 0:
                self.delta_file.write('delete_col : '+str(delv) + ' ')
            for k,v in curr_matrix.items():
                if k not in self.last_matrix:continue
                addv = v.difference(self.last_matrix[k])
                if len(addv) > 0:
                    self.delta_file.write('add_col_'+str(k)+' : '+str(addv))
                self.delta_file.write(' ')
            for k,v in self.last_matrix.items():
                if k not in curr_matrix:continue
                delv = self.last_matrix[k].difference(v)
                if len(delv) > 0:
                    self.delta_file.write('delete_col_'+str(k)+' : '+str(delv))
                self.delta_file.write(' ')
            self.delta_file.write('
')
            self.last_matrix = curr_matrix
        
    
    def _search(self, k):
        # print(f"Size: {k}") # k is depth
        # print(f"Solution: {self.sol_dict}")
        # print("Matrix:")
        # print(self.matrix)

        if self.matrix.header.R == self.matrix.header:
            # matrix is empty, solution found
            if self.callback(self._create_sol(k)):
                self.stop = True
            return

        if self.choose_min:
            col = self.matrix.min_column()
        else:
            col = self.matrix.random_column()

        # cover column col
        #
        row = col.D
        rows = []
        for row in iterate_cell(col, 'D'):
            rows.append(row)
        rows.sort(key=lambda x:x.limit_num,reverse=True)
        self.depth += 1
        for row in rows:
            if col.limit_num < row.limit_num:continue
            isValid = True
            for j in iterate_cell(row, 'R'):
                if j.C.limit_num < j.limit_num:
                    isValid = False
                    break
            if not isValid:
                continue
            self.sol_dict[k] = row
            col.limit_num -= row.limit_num
            row.D.U = row.U
            row.U.D = row.D
            col.size -= 1
            if col.limit_num == 0:
                self.matrix.cover(col)
            for j in iterate_cell(row, 'R'):
                j.C.limit_num -= j.limit_num
                j.D.U = j.U
                j.U.D = j.D
                j.C.size -= 1
                if j.C.limit_num == 0:
                    self.matrix.cover(j.C)
            execYou = True
            for j in iterate_cell(self.matrix.header, 'R'):
                if j.limit_num > j.sum_limit_num:
                    execYou = False
                    break
            if execYou:
                self._search(k + 1)
            if self.stop:
                return
            # uncover columns

            for j in iterate_cell(row, 'L'):
                if j.C.limit_num == 0:
                    self.matrix.uncover(j.C)
                j.C.size += 1
                j.D.U = j.U.D = j
                j.C.limit_num += j.limit_num
            if col.limit_num == 0:
                self.matrix.uncover(col)
            col.size += 1
            row.D.U = row.U.D = row
            col.limit_num += row.limit_num
        self.depth -= 1
        #

    def _create_sol(self, k):
        # creates a solution from the inner dict
        sol = {}
        for key, row in self.sol_dict.items():
            if key >= k:
                continue

            tmp_list = [row.C.name]
            tmp_list.extend(r.C.name for r in iterate_cell(row, 'R'))
            sol[row.indexes[0]] = tmp_list

        return sol
#
def main():
    from_dense = (lambda row:[i for i, el in enumerate(row) if el])
    rows = [from_dense([0, 0, 1, 0, 1, 1, 0]),
            from_dense([1, 0, 0, 1, 0, 0, 1]),
            from_dense([0, 1, 1, 0, 0, 1, 0]),
            from_dense([1, 0, 0, 1, 0, 0, 0]),
            from_dense([0, 1, 0, 0, 0, 0, 1]),
            from_dense([0, 0, 0, 1, 1, 0, 1])]
    size = max(max(rows, key=max)) + 1
    d = DancingLinksMatrix(string.ascii_uppercase[:size])
    for row in rows:
        d.add_sparse_row(row, already_sorted=True)
    AlgorithmX(d, print)()
#
if __name__ == "__main__":
    main()
alg_x.py

  舞蹈表数据结构:

"""
Implementation of Donald Knuth's Dancing Links Sparse Matrix
as a circular doubly linked list. (http://arxiv.org/abs/cs/0011047)
"""

import random
import numpy as np

__author__ = "FunCfans"

class CannotAddRowsError(Exception):
    pass
#
class EmptyDLMatrix(Exception):
    pass
#
class Cell:
    """
    Inner cell, storing 4 pointers to neighbors, a pointer to the column header
    and the indexes associated.
    """
    __slots__ = list("UDLRC") + ["indexes", "limit_num"]

    def __init__(self, limitNum=1):
        self.U = self.D = self.L = self.R = self
        self.C = None
        self.indexes = None
        self.limit_num = limitNum

    def __str__(self):
        return f"Node: {self.indexes}"

    def __repr__(self):
        return f"Cell[{self.indexes}]"


class HeaderCell(Cell):
    """
    Column Header cell, a special cell that stores also a name and a size
    member.
    """
    __slots__ = ["size", "name", "is_first", "sum_limit_num"]

    def __init__(self, name, limitNum=1):
        super(HeaderCell, self).__init__(limitNum)
        self.size = 0
        self.name = name
        self.is_first = False
        self.sum_limit_num = 0

class DancingLinksMatrix:
    """
    Dancing Links sparse matrix implementation.
    It stores a circular doubly linked list of 1s, and another list
    of column headers. Every cell points to its upper, lower, left and right
    neighbors in a circular fashion.
    """

    def __init__(self, columns):
        """
        Creates a DL_Matrix.
        :param columns: it can be an integer or an iterable. If columns is an
                        integer, columns columns are added to the matrix,
                        named C0,...,CN where N = columns -1. If columns is an
                        iterable, the number of columns and the names are
                        deduced from the iterable, else TypeError is raised.
                        The iterable may yield the names, or a tuple
                        (name,primary). primary is a bool value that is True
                        if the column is a primary one. If not specified, is
                        assumed that the column is a primary one.
        :raises TypeError, if columns is not a number neither an iterable.
        """
        self.header = HeaderCell("<H>")
        self.header.is_first = True
        self.rows = self.cols = 0
        self.col_list = []
        self._create_column_headers(columns)

    def _create_column_headers(self, columns):
        if isinstance(columns, int):
            columns = int(columns)
            column_names = ((f"C{i}", 1) for i in range(columns))
        else:
            try:
                column_names = iter(columns)
            except TypeError:
                raise TypeError("Argument is not valid")

        prev = self.header
        # links every column in a for loop
        for name in column_names:
            primary = True
            if isinstance(name, tuple) or isinstance(name, list):
                name, limitNum = name
            else:
                limitNum = 1
            cell = HeaderCell(name, limitNum)
            cell.indexes = (-1, self.cols)
            cell.is_first = False
            self.col_list.append(cell)
            if primary:
                prev.R = cell
                cell.L = prev
                prev = cell
            self.cols += 1

        prev.R = self.header
        self.header.L = prev

    def add_sparse_row(self, row, already_sorted=False):
        """
        Adds a sparse row to the matrix. The row is in format
        [ind_0, ..., ind_n] where 0 <= ind_i < dl_matrix.ncols.
        If called after end_add is executed, CannotAddRowsError is raised.
        :param row: a sequence of integers indicating the 1s in the row.
        :param already_sorted: True if the row is already sorted,
                               default is False. Use it for performance
                               optimization.
        :raises CannotAddRowsError if end_add was already called.
        """
        if self.col_list is None:
            raise CannotAddRowsError()

        prev = None
        start = None

        if not already_sorted:
            row = sorted(row)

        cell = None
        for ind in row:
            if isinstance(ind, int):
                ind = (ind, 1)
            cell = Cell(ind[1])
            cell.indexes = (self.rows, ind[0])

            if prev:
                prev.R = cell
                cell.L = prev
            else:
                start = cell

            col = self.col_list[ind[0]]
            # link the cell with the previous one and with the right column
            # cells.
            last = col.U
            last.D = cell
            cell.U = last
            col.U = cell
            cell.D = col
            cell.C = col
            col.size += 1
            prev = cell
            col.sum_limit_num += ind[1]

        start.L = cell
        cell.R = start
        self.rows += 1

    def end_add(self):
        """
        Called when there are no more rows to be inserted. Not strictly
        necessary, but it can save some memory.
        """
        self.col_list = None

    def min_column(self):
        """
        Returns the column header of the column with the minimum number of 1s.
        :return: A column header.
        :raises: EmptyDLMatrix if the matrix is empty.
        """
        # noinspection PyUnresolvedReferences
        if self.header.R.is_first:
            raise EmptyDLMatrix()

        col_min = self.header.R

        for col in iterate_cell(self.header, 'R'):
            if not col.is_first and col.size < col_min.size:
                col_min = col

        return col_min

    def random_column(self):
        """
        Returns a random column header. (The matrix header is never returned)
        :return: A column header.
        :raises: EmptyDLMatrix if the matrix is empty.
        """
        col = self.header.R
        if col is self.header:
            raise EmptyDLMatrix()

        n = random.randint(0, self.cols - 1)

        for _ in range(n):
            col = col.R

        if col.is_first:
            col = col.R
        return col

    def __str__(self):
        names = []
        m = np.zeros((self.rows, self.cols), dtype=np.uint8)
        rows, cols = set(), []

        for col in iterate_cell(self.header, 'R'):
            cols.append(col.indexes[1])
            # noinspection PyUnresolvedReferences
            names.append(col.name)

            for cell in iterate_cell(col, 'D'):
                ind = cell.indexes
                rows.add(ind[0])
                m[ind] = 1

        m = m[list(rows)][:, cols]
        return "
".join([", ".join(names), str(m)])

    @staticmethod
    def coverRow(r, isadd=False):
        for j in iterate_cell(r, 'R'):
            if j.C.limit_num < j.limit_num:
                return False
        for j in iterate_cell(r, 'R'):
            j.D.U = j.U
            j.U.D = j.D
            j.C.size -= 1
            j.C.sum_limit_num -= j.limit_num
            if isadd:
                j.C.limit_num -= j.limit_num
        return True
    
    @staticmethod
    def checkCover(c):
        subLimitNum = {}
        for i in iterate_cell(c, 'D'):
            for j in iterate_cell(i, 'R'):
                idx = j.indexes[1]
                if idx not in subLimitNum:
                    subLimitNum[idx] = 0
                subLimitNum[idx] += j.limit_num
                if j.C.sum_limit_num - subLimitNum[idx] < j.C.limit_num:
                    return False
                
        return True
    
    @staticmethod
    def cover(c, isadd=False):
        """
        Covers the column c by removing the 1s in the column and also all
        the rows connected to them.
        :param c: The column header of the column that has to be covered.
        """
        # print("Cover column", c.name)
        c.R.L = c.L
        c.L.R = c.R

        for i in iterate_cell(c, 'D'):
            DancingLinksMatrix.coverRow(i, isadd)
        return True

    @staticmethod
    def uncoverRow(r, isadd=False):
        for j in iterate_cell(r, 'L'):
            j.C.sum_limit_num += j.limit_num
            j.C.size += 1
            j.D.U = j.U.D = j
            if isadd:
                j.C.limit_num += j.limit_num
        return True

    @staticmethod
    def uncover(c, isadd=False):
        """
        Uncovers the column c by readding the 1s in the column and also all
        the rows connected to them.
        :param c: The column header of the column that has to be uncovered.
        """
        # print("Uncover column", c.name)
        for i in iterate_cell(c, 'U'):
            DancingLinksMatrix.uncoverRow(i, isadd)

        c.R.L = c.L.R = c


def iterate_cell(cell, direction):
    cur = getattr(cell, direction)
    while cur is not cell:
        yield cur
        cur = getattr(cur, direction)


# TODO to be completed
class MatrixDisplayer:
    def __init__(self, matrix):
        dic = {}

        for col in iterate_cell(matrix.header, 'R'):
            dic[col.indexes] = col

        for col in iterate_cell(matrix.header, 'R'):
            first = col.D
            dic[first.indexes] = first
            for cell in iterate_cell(first, 'D'):
                if cell is not col:
                    dic[cell.indexes] = cell

        self.dic = dic
        self.rows = matrix.rows
        self.cols = matrix.cols

    def print_matrix(self):
        m = {}

        for i in range(-1, self.rows):
            for j in range(0, self.cols):
                cell = self.dic.get((i, j))
                if cell:
                    if i == -1:
                        m[0, 2 * j] = cell.name+','+str(cell.limit_num)
                    else:
                        m[2 * (i + 1), 2 * j] = "X"+','+str(cell.limit_num)

        for i in range(-1, self.rows * 2):
            for j in range(0, self.cols * 2):
                print(m.get((i, j), "   "), end="")
            print()


if __name__ == "__main__":
    def from_dense(row):
        return [i for i, el in enumerate(row) if el]

    r = [from_dense([1, 0, 0, 1, 0, 0, 1]),
         from_dense([1, 0, 0, 1, 0, 0, 0]),
         from_dense([0, 0, 0, 1, 1, 0, 1]),
         from_dense([0, 0, 1, 0, 1, 1, 0]),
         from_dense([0, 1, 1, 0, 0, 1, 1]),
         from_dense([0, 1, 0, 0, 0, 0, 1])]

    d = DancingLinksMatrix("1234567")

    for row in r:
        d.add_sparse_row(row, already_sorted=True)
    d.end_add()

    p = MatrixDisplayer(d)
    p.print_matrix()

    # print(d.rows)
    # print(d.cols)
    # print(d)

    mc = d.min_column()
    # print(mc)

    d.cover(mc)
    # print(d)

    p.print_matrix()
dlmatrix.py

  为了能获取谜面,我们需要把谜面转换为文件,这里的谜面排布与star battle类似,稍微改一下代码就可以用了:

from lxml import etree
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import copy
deepcopy = copy.deepcopy
monthlyUrl = 'https://www.puzzle-aquarium.com/?size=11'
weeklyUrl = 'https://www.puzzle-aquarium.com/?size=10'
dailyUrl = 'https://www.puzzle-aquarium.com/?size=9'
url = 'https://www.puzzle-aquarium.com/?size=8'
#
def getChessByChrome():
    path = r'D:chromedriver.exe'
    chrome_options = Options()
    #后面的两个是固定写法 必须这么写
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    driver = webdriver.Chrome(executable_path=path,chrome_options=chrome_options)
    try:
        driver.set_page_load_timeout(30)
        driver.get(url)
    except Exception as e:
        print(e)
    source = driver.page_source
    driver.quit()
    return source
#
def getChessByFile():
    with open('aquarium.html','r',encoding='utf-8') as f:source=f.read()
    return source
#
def solve():
    if url.find('size=') == -1:
        size = 0
    else:
        size = url.split('size=')[1]
        size = int(size)
    source = getChessByChrome()
    htree = etree.HTML(source)
    chessSize = len(htree.xpath('//div[@id="game"]/div/div'))
    puzzleId = htree.xpath('//div[@class="puzzleInfo"]/p/span/text()')
    if len(puzzleId) != 0:
        puzzleId = puzzleId[0]
    else:
        puzzleId = htree.xpath('//div[@class="puzzleInfo"]/p/text()')[0]
    chessSize = round(chessSize**0.5)
    chess = [[-1 for _ in range(chessSize)] for __ in range(chessSize)]
    borderss = [['' for _ in range(chessSize)] for __ in range(chessSize)]
    chessStr = ''
    maxBlockNumber = 0
    # br: on the right; bl: on the left; bb: on the down; bt: on the up
    for i,className in enumerate(htree.xpath('//div[@id="game"]/div/div[contains(@class,"cell")]')):
        x = i // chessSize
        y = i % chessSize
        value = className.xpath('./@class')[0]
        if value[:4] != 'cell':
            continue
        value = value.replace('cell selectable','')
        value = value.replace('cell-off','')
        borderss[x][y] = value
    for i in range(chessSize):
        for j in range(chessSize):
            if chess[i][j] != -1:
                continue
            queue = [(i, j)]
            chess[i][j] = str(maxBlockNumber)
            while len(queue) > 0:
                oldQueue = deepcopy(queue)
                queue = []
                for pos in oldQueue:
                    x, y = pos[0], pos[1]
                    #
                    if x > 0 and borderss[x][y].find('bt') == -1 and chess[x-1][y] == -1:
                        queue.append((x-1, y))
                        chess[x-1][y] = chess[i][j]
                    #
                    if x < chessSize - 1 and borderss[x][y].find('bb') == -1 and chess[x+1][y] == -1:
                        queue.append((x+1, y))
                        chess[x+1][y] = chess[i][j]
                    #
                    if y > 0 and borderss[x][y].find('bl') == -1 and chess[x][y-1] == -1:
                        queue.append((x, y-1))
                        chess[x][y-1] = chess[i][j]
                    #
                    if y < chessSize - 1 and borderss[x][y].find('br') == -1 and chess[x][y+1] == -1:
                        queue.append((x, y+1))
                        chess[x][y+1] = chess[i][j]
                    #
            maxBlockNumber += 1
    chessStr = '
'.join(' '.join(chessRow) for chessRow in chess)
    chessStr += '
' + ' '.join(htree.xpath('//div[@class="cell task h"]/text()'))
    chessStr += '
' + ' '.join(htree.xpath('//div[@class="cell task v"]/text()'))
    with open('aquarium' + puzzleId + '_' + str(size) + '.txt','w') as f:f.write(chessStr)
#
if __name__ == '__main__':
    solve()
getAquariumChess.py

  抓取10*10 easy,ID为69,467的谜面,储存文件并执行上述代码,结果如下:

+-+-+-+-+-+-+-+-+-+-+
| | | |*|*|*|*|*|*|*|
+-+-+-+-+-+-+-+-+-+-+
| | | | |*| | | | |*|
+-+-+-+-+-+-+-+-+-+-+
| | | |*|*| | |*|*|*|
+-+-+-+-+-+-+-+-+-+-+
| | |*|*|*| | |*|*|*|
+-+-+-+-+-+-+-+-+-+-+
| | |*|*|*|*| | | |*|
+-+-+-+-+-+-+-+-+-+-+
| |*|*|*|*|*|*| | | |
+-+-+-+-+-+-+-+-+-+-+
|*|*|*|*|*|*|*| | | |
+-+-+-+-+-+-+-+-+-+-+
|*|*|*| |*| |*|*|*| |
+-+-+-+-+-+-+-+-+-+-+
|*| |*|*|*|*|*|*|*|*|
+-+-+-+-+-+-+-+-+-+-+
| | | | | |*|*|*|*|*|
+-+-+-+-+-+-+-+-+-+-+
the DLX runtime is : 24.061376333236694s

  与示例谜底相同,大功告成!

  (一定要有版本管理的习惯,不然会吧自己坑进去。这里为了debug有瑕疵的算法,没有保存就把原先可能是正确的代码给修掉了T_T)

原文地址:https://www.cnblogs.com/dgutfly/p/12036659.html