Haskell如何生成所有可能的回文

时间:2017-06-16 22:06:30

标签: haskell palindrome

如何生成长度为n的所有可能的回文?

应该使用唯一的字符['a'..'z']

palindrome n :: Integer -> [String]

2 个答案:

答案 0 :(得分:1)

回文是一个字符串,其中字符的后半部分与字符的前半部分相反。因此,一个简单的算法是生成长度为n / 2的所有字符串,然后将每个字符串的反向追加到末尾。对于奇数长度的回文,我们可以删除字符串后半部分的第一个字符,并确保在找到n / 2时向上舍入。

现在棘手的部分是生成所有可能的长度为n / 2的字符串。我们需要为['a'..'z']中的字符串中的每个字符选择一个字符,并在Haskell中lists can represent non-determinism。因此,我们需要做的就是使用replicateM,它将创建每个字符串,其中每个字符都是从字母表中非确定性地选择的。

附注,任何长度n可能的回文数以指数速率增加。使用Integer作为输入是过度的,因为Int的最大值已经超过9 quintillion。

这是实现完整算法的一种方法:

palindrome :: Int -> [String]
palindrome n
    | n < 0  = []
    | even n = map (\front -> front ++ reverse front) fronts
    | odd n  = map (\front -> front ++ tail (reverse front)) fronts
    where fronts = replicateM (div (n + 1) 2) ['a'..'z']

答案 1 :(得分:0)

甚至案例

为简单起见,首先假设n是偶数,稍后我们可以概括该函数。我们可以使用递归。我们定义一个辅助函数pal' :: Integer -> [String] -> [String]。这里的第二个参数是累积的反向字符串。所以一旦我们点击0,我们只需要发出一个包含累积反转字符串的列表,如:

pal' 0 r = [r]

然而,鉴于我们仍然在回文的生成部分,左侧,我们可以使用列表理解,如:

pal' k r = [ c : p | c <- ['a'..'z'], p <- pal' (k-1) (c:r) ]

所以我们迭代[a..z],对于每个这样的字符,我们在pal'执行递归调用,我们需要生成k-1字符,(c:r)作为要发射的反向弦。此外,我们得到这些回文c : p。因此,我们将选择角色放在回文前面。

现在,对于生成甚至回文的even_palindrome函数,我们可以写一下:

evenpalindrome :: Integer -> [String]
evenpalindrome n = pal' (div n 2) []
    where pal' 0 r = [r]
          pal' k r = [ c : p | c <- ['a'..'z'], p <- pal' (k-1) (c:r) ]

出于测试目的,我将代码设置为从`c&lt; - ''''..'d']范围中选择,但您可以将其设置为您想要的任何范围。

如果我们生成长度为0,2和4的回文,我们得到:

*Main> evenpalindrome 0
[""]
*Main> evenpalindrome 2
["aa","bb","cc","dd"]
*Main> evenpalindrome 4
["aaaa","abba","acca","adda","baab","bbbb","bccb","bddb","caac","cbbc","cccc","cddc","daad","dbbd","dccd","dddd"]

所以这似乎有效。但是,如果我们写evenpalindrome 1,那么它将起到整数除法的作用,从而生成长度为0的回文。

奇数案例

现在的问题是我们必须改变什么才能让它适用于奇数长度。这里有两件事需要改变:

  1. 我们需要生成一个额外的字符,因此我们不应该使用div n 2,而是使用div (n+1) 2;和
  2. 在相反的情况下,重复生成的最后一个字符。
  3. 所以这意味着我们应该首先检查n模2(让它为d),然后重写:

    pal' 0 r = [drop (fromInteger d) r]
    

    此外如前所述,我们应该使用pal'调用初始pal' (div (n+1) 2) [],所以现在通用版本是:

    palindrome :: Integer -> [String]
    palindrome n = pal' (div (n+1) 2) []
        where pal' 0 r = [drop (fromInteger d) r]
              pal' k r = [ c : p | c <- ['a'..'z'], p <- pal' (k-1) (c:r) ]
              d = mod n 2
    

    产生:

    *Main> palindrome 1
    ["a","b","c","d"]
    *Main> palindrome 2
    ["aa","bb","cc","dd"]
    *Main> palindrome 3
    ["aaa","aba","aca","ada","bab","bbb","bcb","bdb","cac","cbc","ccc","cdc","dad","dbd","dcd","ddd"]
    *Main> palindrome 4
    ["aaaa","abba","acca","adda","baab","bbbb","bccb","bddb","caac","cbbc","cccc","cddc","daad","dbbd","dccd","dddd"]
    *Main> palindrome 5
    ["aaaaa","aabaa","aacaa","aadaa","ababa","abbba","abcba","abdba","acaca","acbca","accca","acdca","adada","adbda","adcda","addda","baaab","babab","bacab","badab","bbabb","bbbbb","bbcbb","bbdbb","bcacb","bcbcb","bcccb","bcdcb","bdadb","bdbdb","bdcdb","bdddb","caaac","cabac","cacac","cadac","cbabc","cbbbc","cbcbc","cbdbc","ccacc","ccbcc","ccccc","ccdcc","cdadc","cdbdc","cdcdc","cdddc","daaad","dabad","dacad","dadad","dbabd","dbbbd","dbcbd","dbdbd","dcacd","dcbcd","dcccd","dcdcd","ddadd","ddbdd","ddcdd","ddddd"]
    

    内存结构

    使用递归方式构造反向部分的一个好处是,所有回文的后半部分都能更有效地存储。鉴于我们为`['a'..'b']范围生成长度为5的回文,最终列表(完成评估后)将如下所示:

    +---+
    | o--- 'a' -- 'a' -> 'a' -\
    +---+                      > 'a' -\
    | o--> 'a' -> 'a' -> 'b' -/        \
    +---+                               > 'a'
    | o--> 'a' -> 'b' -> 'a' -\        /
    +---+                      > 'b' -/
    | o--> 'a' -> 'b' -> 'b' -/
    +---+
    | o--> 'b' -> 'a' -> 'a' -\
    +---+                      > 'a' -\
    | o--> 'b' -> 'a' -> 'b' -/        \
    +---+                               > 'b'
    | o--> 'b' -> 'b' -> 'a' -\        /
    +---+                      > 'b' -/
    | o--> 'b' -> 'b' -> 'b' -/
    +---+