我正在编写一个Haskell程序,该程序读取英语单词列表和矩形字母网格,如:
I T O L
I H W S
N H I S
K T S I
然后从左上角通过网格找到一个Hamiltonian path,用于拼出一系列英文单词,例如:
--> $ runghc unpacking.hs < 4x4grid.txt
I THINK THIS IS SLOW
(如果有多个解决方案,它可以打印任何找到的解决方案并停止查找。)
天真,严格的方法是生成一条完整的路径,然后尝试将其拆分为单词。但是,假设我正在这样做(目前我正在强迫自己 - 见下文),我花了很多时间寻找路径:
IINHHTOL...
IINHHTOW...
IINHHWOL...
这些显然永远不会变成文字,看着前几个字母("IINH"
不能分成单词,而且没有英文单词包含"NHH"
。)所以,说,在上面的网格中,我不想查看以IINHH
开头的许多 [1] 路径。
现在,我的功能看起来像这样:
paths :: Coord -> Coord -> [[Coord]]
paths (w, h) (1, 1) = [[(1, 1), (1, 2), ... (x, y)], ...]
lexes :: Set String -> String -> [[String]]
lexes englishWordset "ITHINKTHISWILLWORK" = [["I", "THINK", "THIS", ...], ...]
paths
只是在(w, h)
网格上找到值得考虑的所有路径。 lexes
找到了删除短语的所有方法,并定义为:
lexes language [] = [[]]
lexes language phrase = let
splits = tail $ zip (inits phrase) (tails phrase)
in concat [map (w:) (lexes language p') | (w, p') <- splits,
w `S.member` language]
鉴于"SAMPLESTRING"
,它会查看"S"
,然后是"SA"
,然后是"SAM"
...一旦找到有效字,它会递归并尝试“ lex“其余的字符串。 (首先它会在"PLESTRING"
上递归并尝试用"SAM"
制作短语,但是找不到将“plestring”剁成单词并失败的方法;然后它会找到["SAMPLE", "STRING"]
。)
当然,对于上面的无效字符串,遵循这种方法会失去任何“懒惰”的希望:在前面的示例中,我们仍然需要搜索超出像"ITOLSHINHISIST"
这样荒谬的短语,因为可能{ {1}}(一个字母更长)可能形成有效的单个字。
我觉得我可以在某种程度上使用懒惰来提高整个程序的性能:如果"ITOLSHINHISISTK"
的前几个字符不是任何字的前缀,我们可以保释完全退出,停止评估phrase
的其余部分,以及剩余的路径。 [2] 这是否有意义?是否有一些树状的数据结构可以帮助我检查不是集合成员资格,但设置“prefix-ness”,从而使检查有效性变得更加懒惰?
[1]显然,对于一个4x4网格,其中很少有这些,但这个论点是关于一般情况:对于更大的网格,我可以跳过成千上万的路径,我看到他们开始时“剑侠情缘”。
对于从输入文件中读取的某些phrase
phrase
,[2] map (grid M.!) path
仅为Map Coord Char
。