我是一名本科编程学生,我正在尝试创建一个类似于“糖果粉碎”的程序。我试图在这里翻译这段代码,以便了解如何搜索可能的动作。下面是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,我将看看你提出的代码片段。非常感谢。
谢谢大家谢谢谢谢!
答案 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)) ])
这是一个我只能假设正在搜索您的代码中相同的最小/最大值的函数。函数定义的左侧就像解构赋值一样。 (any
和groupby
是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
,同时还将元组的元素提取到i1
和i2
。
函数works
然后调用lookaround
两次(对于移动的每个元素一次),以确定any
是length
是否>=3
匹配}。为此,lookaround
再次使用列表推导,选择有关传入坐标对的行和列元素。注意:其他语言可能使用array[index]
,此处的索引操作为array ! index
。
函数lookaround
使用标准函数groupBy
将行(和列)拆分为基于函数combineable
的组(如flipstones
,函数combineable
似乎是用户在程序中的其他位置定义的)。为了方便起见,它连接这两个组列表(使用list-concatenation运算符++
),然后检查是否有任何组长度为3或更长。
正如你自己猜测的那样,这个Haskell代码似乎并没有特别优化,但它很简单:一旦你弄清楚代码的作用,很明显它正确返回一个包含所有可能的列表用户移动,没有其他移动。