草庐IT

python - 如何为第 9 题制作一个高效的解算器

coder 2023-08-22 原文

iOSAndriod 上有一款名为Puzzle Number 9 的游戏(我与创作者没有任何关系)。您从 3x3 网格开始,其中数字 1 到 9 随机放置在棋盘上。然后将相邻数字(追踪一条路径)组合起来,加起来为 9。路径中的最后一个节点变为 9,所有其他数字增加 1。将 9 的相同倍数组合在一起,其中结束节点变为数字的两倍并且起始节点回到一个。例如,如果您开始于

1 2 3
5 4 6
7 8 9 

你可以从 2-3-4 开始到结束

1 3 4
5 9 6
7 8 9

然后将两个9组合起来

1 3 4
5 1 6
7 8 18

游戏的目标是达到 1152。基本上它就像 2048,但没有随机元素。例如,当您用完总和为 9 的数字时游戏结束

8 7 6
5 5 5
9 1 72

我在 python 上写了一个简单的深度优先搜索,它适用于一些谜题,但我用完了其他谜题的内存:

import sys
import Queue

conf = "213 547 689"
grid1 = []
for y in conf.split():
    for x in y:
        grid1.append(int(x))

a = []
explored = set()
sol = Queue.LifoQueue()

def tostr(node):
    s = ''
    for i in range(0,9):
        s += str(node[i]) + ' '
    return s

def printsol():
    while not sol.empty():
        print sol.get()        

def same(x, y, grid):
    for n in neighbors(y):
        ng = grid[:]
        if grid[n] == x and n != y:
            if x == 576:
                printsol()
                sys.exit()
            ng[n] = 2*x
            ng[y] = 1
            sol.put(tostr(ng))
            same(2*x, n, ng)
            solve(ng, grid)
            sol.get()
            ng[n] = 1
            ng[y] = 2*x
            sol.put(tostr(ng))
            same(2*x, y, ng)
            solve(ng, grid)
            sol.get()

##succeeding functions are edited versions of Boggle solver from http://stackoverflow.com/questions/746082/how-to-find-list-of-possible-words-from-a-letter-matrix-boggle-solver
def solve(grid2, grid1):
    for i in range(0,9):
        if grid2[i] < 9 and tostr(grid2) not in explored:
            for result in extending(grid2[i], (i,), grid2):
                newgrid = grid2[:]
                y = len(result) - 1
                for j in range(0, y):
                    newgrid[result[j]] += 1
                newgrid[result[y]] = 9
                sol.put(tostr(newgrid))
                if tostr(newgrid) not in explored:
                    same(9, result[y], newgrid)
                    solve(newgrid, grid2)
                sol.get()
    explored.add(tostr(grid2))

def extending(add, path, grid2):
    if add == 9:
        yield path
    for n in neighbors(path[-1]):
        if n not in path:
            add1 = add + grid2[n]
            if add1 <= 9:
                for result in extending(add1, path + (n,), grid2):
                    yield result

def neighbors(n):
    for nx in range(max(0, n%3-1), min(n%3+2, 3)):
        for ny in range(max(0, n/3-1), min(n/3+2, 3)):
            yield ny*3+nx

sol.put(tostr(grid1))
solve(grid1, grid1)

如何提高效率?如果不是那样,什么是用于知情搜索的良好启发式方法?我正在考虑取板上数字的平均值与某个数字的绝对差,但什么是好的数字?

最佳答案

The goal of the game is to reach 1152

我已经设法做到了!这是搜索的示例输出。第一行的三个数字是:状态得分、图的深度和矩阵中的最大元素。接下来的三个是矩阵本身:

...
-577 246 576
  36  288    9 
   1    1  576 
 144   36   72 

-577 245 576
  36  288   18 
  18    1  576 
 144    9   72 

-577 249 576
   1  288    9 
   1  288  576 
   1    1    1 

-577 245 576
  36  288    1 
  18   18  576 
 144    9   72 

-577 250 576
   1    1    9 
   1  576  576 
   1    1    1 

-1153 251 1152
   1    1    9 
   1    1 1152 
   1    1    1 

-1153 251 1152
   1    1    9 
   1 1152    1 
   1    1    1 

-577 250 576
   1  576    9 
   1    1  576 
   1    1    1 

-1153 251 1152
   1    1    9 
   1    1 1152 
   1    1    1 

-1153 251 1152
   1 1152    9 
   1    1    1 
   1    1    1 
...

如您所见,为了获得 1152 的分数,您必须深入探索。还要考虑到分支因子非常大,您会发现问题具有挑战性。您必须完成 ~250 步才能获得 1152 的分数。假设您每次移动需要 10 秒,您将玩 40 分钟!!!

我所做的是为每个节点/状态关联一个分数,在探索期间我只保留前 1000 个节点/状态。这确保我们只探索相关节点。 所以剩下的就是设计评分函数/启发式方法。这是我尝试过的评分功能:

  • 矩阵中的最大元素
  • 节点深度
  • 矩阵中元素的总和
  • score 表示有多少元素可以被 9 整除,相邻元素也可以被 9 整除
  • 矩阵中不同元素的数量
  • 9以上矩阵中不同元素的个数
  • 2以下矩阵中不同元素的数量
  • score 表示有多少元素可以被 9 整除,相邻元素也可以被 9 整除并且它的两倍小

使用简单的启发式方法,您可以构建更复杂的方法。我最终得到的是启发式方法,它只是上述列表中第一个和最后一个的总和。选择更具限制性的启发式算法会使搜索变得困惑。这是代码(python 3):

import random
from queue import PriorityQueue


dx = [0,  0, -1, -1, -1,  1, 1, 1]
dy = [-1, 1, -1,  0,  1, -1, 0, 1]


class State:
    def __init__(self, a, depth):
        self.depth = depth
        if len(a) == 9:
            self.a = (tuple(a[:3]), tuple(a[3:6]), tuple(a[6:]))
        if len(a) == 3:
            self.a = tuple(map(tuple, a))

    @staticmethod
    def check(i):
        return 0 <= i < 3

    def get_9_moves(self):
        ans = []
        for i in range(3):
            for j in range(3):
                if self.a[i][j] % 9 == 0:

                    for k in range(len(dx)):
                        ni, nj = i + dx[k], j + dy[k]
                        if State.check(ni) and State.check(nj):
                            if self.a[ni][nj] % 9 == 0 and \
                               self.a[i][j] == self.a[ni][nj]:
                                ans.append(((i, j), (ni, nj)))
        return ans

    def get_sum_moves_rec(self, i, j, cur_sum, cur_cells, ans):
        if cur_sum > 9:
            return
        if cur_sum == 9 and len(cur_cells) > 1:
            ans.append(tuple(cur_cells))
        for k in range(len(dx)):
            ni, nj = i + dx[k], j + dy[k]
            pair = (ni, nj)
            if self.check(ni) and self.check(nj) and pair not in cur_cells:
                cur_cells.append(pair)
                self.get_sum_moves_rec(ni, nj, cur_sum + self.a[ni][nj],  cur_cells, ans)
                cur_cells.pop()

    def get_sum_moves(self):
        ans = []
        for i in range(3):
            for j in range(3):
                self.get_sum_moves_rec(i, j, self.a[i][j], [(i, j)], ans)
        return ans

    def get_neighbours(self):
        neighbours = []
        moves = self.get_sum_moves()
        for move in moves:
            a = list(map(list, self.a))
            for i in range(len(move)):
                x, y = move[i]
                if i == len(move)-1:
                    a[x][y] = 9
                else:
                    a[x][y] += 1
            neighbours.append(State(a, self.depth+1))
        moves = self.get_9_moves()
        for move in moves:
            a = list(map(list, self.a))
            a[move[0][0]][move[0][1]] = 1
            a[move[1][0]][move[1][1]] *= 2
            neighbours.append(State(a, self.depth+1))
        return neighbours

    def get_value(self):
        return max(map(max, self.a))

    # 576
    def get_score0(self):
        return -self.get_value()

    def get_score1(self):
        ans = 0
        for i in range(3):
            for j in range(3):
                if self.a[i][j] % 9 == 0:
                    ans += 1
        return ans

    # 36
    def get_score2(self):
        ans = 0
        for i in range(3):
            for j in range(3):
                if self.a[i][j] < 9:
                    ans += 1
        return ans

    # achieves 1152 but rather slow
    def get_score3(self):
        return -self.depth

    # 36
    def get_score4(self):
        ans = 0
        for i in range(3):
            for j in range(3):
                if self.a[i][j] == 1:
                    ans += 1
        return ans

    # 288
    def get_score5(self):
        return -sum(map(sum, self.a))

    def get_score6(self):
        t = []
        for i in range(3):
            for j in range(3):
                if self.a[i][j] % 9 == 0:
                    t.append((self.a[i][j], (i, j)))
        t.sort(reverse=True)
        ans = 0
        for i in range(len(t) - 1):
            pairi = t[i][1]
            pairj = t[i+1][1]

            if abs(pairi[0]-pairj[0]) <= 1 and abs(pairi[1]-pairj[1]) <= 1:
                ans += 1
            break
        return -ans

    def get_score7(self):
        t = []
        for i in range(3):
            for j in range(3):
                if self.a[i][j] % 9 == 0:
                    t.append(self.a[i][j])
        t.sort(reverse=True)
        ans = 0
        for i in range(len(t) - 1):

            if t[i] // t[i+1] == 2:
                ans += 1
            break
        return -ans

    def get_score8(self):
        t = []
        for e in self.a:
            t.extend(e)
        return len(list(filter(lambda x: x >= 9,t)))

    def get_score9(self):
        t = []
        for e in self.a:
            t.extend(e)
        return len(list(filter(lambda x: x <= 2,t)))

    def get_score10(self):
        t = []
        for e in self.a:
            t.extend(e)
        return len(set(filter(lambda x: x >= 9,t)))



    def get_score_mix(self):
        # achieves 1152
        return self.get_score0() + self.get_score6()

    def __repr__(self):
        ans = ''
        for i in range(3):
            for j in range(3):
                ans += '{0:4d} '.format(self.a[i][j])
            ans += '\n'
        return ans

    def __hash__(self):
        return  hash(self.a)

    def __lt__(self, other):
        # hash(self) < hash(other)
        pass


class Solver:

    @staticmethod
    def strategy1(s):
        visited = set()
        while True:
            # print(s)
            neighbours = s.get_neighbours()
            if len(neighbours) == 0:
                break
            ns = None
            for i in range(30):
                ns = random.choice(neighbours)
                if not ns in visited:
                    s = ns
                    break
            if ns is None:
                break
        print(s.get_value())

    @staticmethod
    def strategy2(s):
        visited = set()
        max_depth = 6
        max_value = 0
        calls = 0

        def dfs(state, depth):
            # print(state)
            nonlocal max_value, calls

            calls += 1
            if depth > max_depth:
                return
            if state in visited:
                return
            visited.add(state)
            max_value = max(max_value, s.get_value())

            for new_state in state.get_neighbours():
                dfs(new_state, depth + 1)

        dfs(s, 0)
        print(max_value)
        print(calls)

    @staticmethod
    def strategy3(s):

        visited = set()
        pq = PriorityQueue(maxsize=1000)
        pq.put((0, s))
        visited.add(s)
        max_value = 0

        while not pq.empty():
            score, state = pq.get()
            # print(' score0  score1  score2  score3  score4  score5  score6  score7  score8  score9 score10')
            # print('{0:7d} {1:7d} {2:7d} {3:7d} {4:7d} {5:7d} {6:7d} {7:7d} {8:7d} {9:7d} {10:7d}'.format(state.get_score0(), state.get_score1(), state.get_score2(),
            #                                                                                              state.get_score3(), state.get_score4(), state.get_score5(),
            #                                                                                              state.get_score6(), state.get_score7(), state.get_score8(),
            #                                                                                              state.get_score9(), state.get_score10()))
            print(score, state.depth, state.get_value())
            print(state)
            max_value = max(max_value, state.get_value())

            for new_state in state.get_neighbours():
                # print(random.randint(0, 10))
                if new_state not in visited:
                    visited.add(new_state)
                    pq._put((new_state.get_score_mix(), new_state))
        print(max_value)


start = list(range(1, 10))
random.shuffle(start)
s = State(start, 0)
Solver.strategy3(s)
# s  = State([1,    1,   18, 18,   18,   36, 18 ,  18,    2  ], 0)
# print(s)
#
# for n in s.get_neighbours():
#     print(n)

下一步是什么?

由于该过程的性能不是很好(找到答案大约需要 1 分钟),因此可以尝试找到对搜索有用的更好的启发式方法。看起来他们应该非常宽松地尝试对需求建模,否则他们会搞砸搜索。

关于python - 如何为第 9 题制作一个高效的解算器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33035471/

有关python - 如何为第 9 题制作一个高效的解算器的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  3. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  4. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  5. ruby - 如何为 emacs 安装 ruby​​-mode - 2

    我刚刚为fedora安装了emacs。我想用emacs编写ruby。为ruby​​提供代码提示、代码完成类型功能所需的工具、扩展是什么? 最佳答案 ruby-mode已经包含在Emacs23之后的版本中。不过,它也可以通过ELPA获得。您可能感兴趣的其他一些事情是集成RVM、feature-mode(Cucumber)、rspec-mode、ruby-electric、inf-ruby、rinari(用于Rails)等。这是我当前用于Ruby开发的Emacs配置:https://github.com/citizen428/emacs

  6. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  7. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  8. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐