Haskell算法/糖果粉碎可能的移动

时间:2014-02-26 23:30:32

标签: python haskell

我是一名本科编程学生,我正在尝试创建一个类似于“糖果粉碎”的程序。我试图在这里翻译这段代码,以便了解如何搜索可能的动作。下面是Haskell中的一段代码(尽管不完全确定)

possibleMoves gf = filter works $ filter valid $ allMoves
where allMoves           = concat [ [((i,j),(i+1,j)), ((i,j),(i,j+1))] | (i,j) <- range ((0,0),(9,9)) ]
      valid (_,(i,j))    = i < 10 && j < 10 -- first coordinate is always correct
      works move@(i1,i2) = let gf' = flipStones gf move
                           in  lookAround i1 gf' || lookAround i2 gf'

lookAround (i,j) gf' = any ((>=3).length) $
    (groupBy combineable $ [ gf' ! (i',j) | i' <- range (max 0 (i-2), min 9 (i+2)) ]) ++
    (groupBy combineable $ [ gf' ! (i,j') | j' <- range (max 0 (j-2), min 9 (j+2)) ])

但是,无论我如何尝试翻译它(显然我对Haskell一无所知),这对我来说毫无意义。这是因为我无法搜索那些符号的含义,我不知道该怎么做。虽然我知道在这些问题上寻求帮助有点蹩脚,但这是一个学校项目,我想我没有那么多时间来学习Haskell的基础知识。任何人都可以帮我找到真相(关于这个功能的作用/如何在我自己找到解决方案的想法等)?或者也许给我一些想法,让我自己创造一个新的好功能。

感谢您的时间


(由OP编辑)

非常感谢你!这两个答案都非常详细和准确,我正在尝试根据提供的数据创建一个新功能,现在在这些帮助之后实现这一目标要容易得多!

另外,kobejohn,我将看看你提出的代码片段。非常感谢。

谢谢大家谢谢谢谢!

2 个答案:

答案 0 :(得分:5)

我知道你不想翻译,所以我在python中提供了一个大致相当的实现,使用python生成器和习语,试图说明基于延迟/流的结果生成的概念。

鉴于您正试图了解其工作原理,让我们分别看一下每个部分。我已经列出了使代码更易于理解的代码,并添加了类型签名,因此您可以了解这些部分如何组合在一起。您可以查找Learn You A Haskell For Great Good中使用的符号。

type Position = (Int, Int)
type Move = (Position, Position)

possibleMoves :: Position -> [Move]
possibleMoves gf = filter works $ filter valid $ allMoves
    where

        allMoves :: [Move]
        allMoves = concat [ [ ((i,j),(i+1,j))
                            , ((i,j),(i,j+1)) ]
                          | (i,j) <- range ((0,0),(9,9)) ]

        valid :: Move -> Bool
        valid (_,(i,j)) = i < 10 && j < 10

        works :: Move -> Bool
        works move@(i1,i2) = let gf' = flipStones gf move
                             in lookAround i1 gf' || lookAround i2 gf'

此函数首先使用列表推导生成所有可能移动的列表(绑定为allMoves)。 haskell中的语法与python的列表推导略有不同。由于haskell的懒惰语义,这段代码最好被认为是一个返回所有可能移动流的生成器。

def allMoves():
    for i in range(0,9):
        for j in range(0,9):
            yield ((i,j),(i+1,j))
            yield ((i,j),(i,j+1))

然后有一个函数valid,它检查移动是否合法,并根据答案返回True / False。

def valid(move):
    return move[1][0] < 10 && move[1][2] < 10

最后,一个函数works,它检查结果是否真的有用。

def works(move):
    # flipStones returns a new game_field that incorporates the move we're testing
    new_gf = flipStones(game_field, move)
    return lookAround(move[0], new_gf) || lookaround(move[1], new_gf)

最后,这些功能都链接在一起,以提供最终答案。 $符号乍一看似乎令人困惑,但只是把它想象成一个管道操作员,管道值从右到左。可以用括号轻松替换它。

possibleMoves gf = filter works $ filter valid $ allMoves
-- Is exactly equivalent to
possibleMoves gf = filter works ( filter valid ( allMoves ) )

where子句中的函数仅存在于possibleMoves的范围内。这很好地映射到python内部函数,如你所见。

from itertools import ifilter

# possibleMoves takes
def possibleMoves(game_field):

    def allMoves():
        for i in range(0,9):
            for j in range(0,9):
                yeild ((i,j),(i+1,j))
                yield ((i,j),(i,j+1))

    def valid(move):
        return move[1][0] < 10 && move[1][3] < 10

    def works(move):
        # the gf in scope here is the gf passed to possibleMoves
        new_gf = flipStones(game_field, move)
        return lookAround(move[0], new_gf) && lookAround(move[1], new_gf)

    return ifilter(works, ifilter(valid, allMoves()))

接下来,我们来看看lookAround

lookAround :: Position -> Position -> Bool
lookAround (i,j) gf' = any ((>=3).length) $
    (groupBy combineable $ [ gf' ! (i',j) | i' <- range (max 0 (i-2), min 9 (i+2)) ]) ++
    (groupBy combineable $ [ gf' ! (i,j') | j' <- range (max 0 (j-2), min 9 (j+2)) ])

这是一个我只能假设正在搜索您的代码中相同的最小/最大值的函数。函数定义的左侧就像解构赋值一样。 (anygroupby是python的标准配置)

from itertools import groupby

def lookAround(pos1, pos2):
    i, j = pos1[0], pos1[1]
    # look around 2 above/below, grouping into runs of colors, returns list of lists
    list1 = groupby([pos2[(i_, j)] for i_ in range(max(0,i-2), min(9,i+2))])
    # look around 2 left right, grouping into runs of colors, returns list of lists
    list2 = groupby([pos2[(i, j_)] for j_ in range(max(0,j-2), min(9,j+2))])
    # return true if there's a run of 3 or more colours in either direction
    return any(lambda l: len(l)>=3, list1 + list2)

我希望这有助于您了解正在发生的事情。这种实现速度的关键是使用延迟生成的列表(python中的生成器)。这意味着一旦知道不需要结果就可以丢弃结果,或者导致无效的答案。这样做的结果是你只需要做实际需要的工作,缺点是在python中,你必须熟悉生成器(也称为协同程序)和面向流的编程。

祝你好运,我希望这能为你提供一些提高实施效果的想法。

答案 1 :(得分:3)

顾名思义,allMoves是一个包含所有可能移动的列表,无论它们是否被允许用户使用。它是从所有可能的坐标对(range (0,0) (9,9))列表开始生成的,并生成水平(((i,j),(i+1,j)))和垂直(((i,j),(i,j+1)))交换。

生成这些交换的循环是使用Haskell的list comprehension语法实现的。这些交换在2的列表中生成(从每个坐标水平和垂直),然后使用concat将它们组合成单个列表。 (恕我直言,这是糟糕的Haskell风格,比严格必要的更令人困惑:一旦他们开始使用列表理解,他们就不必使用concat来实现这一点......)

从这个列表开始,他们使用标准函数filter两次来删除他们不想要的列表元素。 filter的第一个参数是用于对元素进行排序的布尔函数,第二个参数是列表本身;它返回一个仅包含函数返回True的元素的列表。

filter的第一次使用使用函数valid,并消除了引用坐标范围之外的元素的移动。

filter的第二次使用使用函数works,它看起来确定移动是否会导致匹配。值gf(它是整体函数possibleMoves的输入)应该是游戏区域,works函数首先从中计算修改的游戏区域gf' = flipstones gf move - 将预期移动应用于原始字段(函数flipstones应该在程序的其他位置由用户定义)。符号move@(i1,i2)将传入的移动绑定到move,同时还将元组的元素提取到i1i2

函数works然后调用lookaround两次(对于移动的每个元素一次),以确定anylength是否>=3匹配}。为此,lookaround再次使用列表推导,选择有关传入坐标对的行和列元素。注意:其他语言可能使用array[index],此处的索引操作为array ! index

函数lookaround使用标准函数groupBy将行(和列)拆分为基于函数combineable的组(如flipstones,函数combineable似乎是用户在程序中的其他位置定义的)。为了方便起见,它连接这两个组列表(使用list-concatenation运算符++),然后检查是否有任何组长度为3或更长。


正如你自己猜测的那样,这个Haskell代码似乎并没有特别优化,但它很简单:一旦你弄清楚代码的作用,很明显它正确返回一个包含所有可能的列表用户移动,没有其他移动。