【问题标题】:Longest common prefix in HaskellHaskell 中最长的公共前缀
【发布时间】:2014-02-12 02:54:25
【问题描述】:

我正在尝试根据最长的公共前缀 cpfx 匹配文件,并且对 haskell 有点陌生。我正在尝试获取列表列表并简单地返回它们共享的前缀。例如:

cpfx ["obscure","obscures","obscured","obscuring"] --> "obscur"

cpfx ["abc", "ab", "abcd"] --> "ab"

我正在尝试使用几个辅助方法,如下所示:

cpfx :: [[Char]] -> [Char]
cpfx [] = [] -- nothing, duh
cpfx (x:[]) = x -- only one thing to test, return it
cpfx (x:t) = cpfx' (x:t) 0 -- otherwise, need to test

cpfx' (x:[]) _ = []
cpfx' (x:t) n
-- call ifMatch to see if every  list matches at that location, then check the next one
      | ifMatch (x:t) n = x!!n + cpfx' x (n+1)
      | otherwise = []

-- ifMatch means if all words match at that location in the list
ifMatch (x:[]) _ = True
ifMatch (x:xs:[]) n = x!!n == xs!!n
ifMatch (x:xs:t) n
      | x!!n == x!!n = ifMatch xs n
      | otherwise = False

但我收到错误消息: Occurs check: cannot construct the infinite type: a0 = [a0]

我猜这与ifMatch (x:t) n = x!!n + cpfx' x (n+1) 行有关。

我能做些什么来补救这种情况?

【问题讨论】:

  • 离题:cpfx [] = [] -- nothing, duh 并不是那么“废话”。空[[a]] 的公共前缀是未定义的,即公共前缀可以是anything,因为没有任何东西可以作为前缀进行测试。

标签: list haskell prefix


【解决方案1】:

如何解决这些错误

注意:虽然我将向您展示如何理解和解决这些错误,但我还在下面提供了一个更优雅的版本(至少从我的角度来看)。

当你最终得到一个无限类型时,最好添加类型签名:

cpfx'   :: [[Char]] -> Int -> [Char]
ifMatch :: [[Char]] -> Int -> Bool

突然间,我们获得了额外的错误,其中两个

  | ifMatch (x:t) n = x!!n + cpfx' x (n+1)
无法将预期类型“[Char]”与实际类型“Char”匹配 预期类型:[[Char]] 实际类型:[字符] 在 `(!!)' 的第一个参数中,即 `x' 在 `(+)' 的第一个参数中,即 `x !! n'
 (Num [Char]) 没有实例
      由使用“+”引起

还有一个ifMatch

  | x!!n == x!!n = ifMatch xs n
 无法将预期类型“[Char]”与实际类型“Char”匹配
    预期类型:[[Char]]
      实际类型:[字符]
    在 `ifMatch' 的第一个参数中,即 `xs'
    在表达式中:ifMatch xs n

现在,cpfx' 中的错误非常简单:x[Char]x !! nChar,并且想将其添加到列表中,因此请使用 : 而不是 @ 987654334@。此外,您想将cpfx' 应用于t,而不是x。这也解决了您的第二个错误。在ifMatch 中,x!!n == x!!n 是多余的,xs 的类型为[Char],因此没有适合ifMatch 的类型。这也是笔误:

  | x!!n == xs!!n = ifMatch t n

但是,既然我们修复了这些编译错误,那么您的程序真的有意义吗?特别是,您希望这些行做什么:

ifMatch (x:xs) n = x!!n : cpfx' xs (n+1)

(x:xs) 是你的话列表。但是,您在每次迭代中都从您的单词中删除了一个单词,这显然不是您的意思。你想要的

ifMatch (x:xs) n = x!!n : cpfx' (x:xs) (n+1)

总的来说我们得到以下代码:

cpfx :: [[Char]] -> [Char]
cpfx []     = []
cpfx [x]    = x
cpfx (x:xs) = cpfx' (x:xs) 0
 
cpfx' :: [[Char]] -> Int -> [Char]
cpfx' [x]    _ = []
cpfx' (x:xs) n
  | ifMatch (x:xs) n = x!!n : cpfx' (x:xs) (n+1)
  | otherwise = []

ifMatch :: [[Char]] -> Int -> Bool
ifMatch [x]      _ = True
ifMatch [x,y]    n = x!!n == y!!n
ifMatch (x:y:xs) n
      | x!!n == y!!n = ifMatch xs n
      | otherwise = False

使用折叠的更简单方法

让我们的函数更简单一点,但也更通用,为任何类型编写一个commonPrefix,实现==

commonPrefix :: (Eq e) => [e] -> [e] -> [e]
commonPrefix _ [] = []
commonPrefix [] _ = []
commonPrefix (x:xs) (y:ys)
  | x == y    = x : commonPrefix xs ys
  | otherwise = []

如果您不习惯这种表示法,请暂时将e 视为Char。现在,一些词的共同前缀可以写成:

"hello" `commonPrefix` "hell" `commonPrefix` "hero"

现在的问题是,如果你想为一系列事情做某事,你通常使用fold

foldl :: (a -> b -> a) -> a -> [b] -> a

foldl,应用于二元运算符、起始值(通常是运算符的左标识)和列表,使用二元运算符从左到右减少列表:

foldl f z [x1, x2, ..., xn] == (...((z `f` x1) `f` x2) `f`...) `f` xn

最后一个例子看起来就像我们之前的`commonPrefix` 行!但是,我们没有起始值,因此我们将使用列表的第一个元素。幸运的是,已经有了foldl1,它就是这样做的。因此,我们之前复杂的函数归结为:

commonPrefixAll :: (Eq a) => [[a]] -> [a]
commonPrefixAll = foldl1 commonPrefix

您应该记住的是:当您想要遍历列表中的多个元素以提供单个值时,请考虑是否真的有必要在每次迭代中查看所有元素。通常,一次只关注两个元素然后使用正确的折叠就足够了。有关更多示例和信息,请参阅Computing one answer over a collection in Real World Haskell 部分。

【讨论】:

    【解决方案2】:

    你可以很容易地避免使用显式递归:

    import Data.Maybe (isJust, fromJust)
    commonPrefix = map fromJust . takeWhile isJust . map the . transpose' 
    

    the 接受一个列表,如果列表的元素不同则返回Nothing,否则返回唯一元素:

    the :: Eq a => [a] -> Maybe a
    the [] = Nothing
    the (x:xs) 
      | and $ map (==x) xs = Just x
      | otherwise          = Nothing
    

    transpose' 类似于Data.List.transpose,但它将结果截断为最短列表的长度:

    transpose' xs = maybe [] id $ do
      ys <- mapM ht xs
      return $ (map fst ys) : (transpose' (map snd ys))
        where 
          ht [] = Nothing
          ht (x:xs) = Just (x,xs)
    

    transpose ["abc", "ab", "abcd"] == ["aaa","bbb","cc","d"]transpose' ["abc", "ab", "abcd"] == ["aaa","bbb"]

    【讨论】:

      【解决方案3】:

      你说的那一行确实有问题。

      注意cpfx' 的参数是(x:[]),即所有与x 具有相同类型的事物的列表,但您的递归调用只使用x。因此你得到了无限类型错误:你试图用[x]的类型来识别x的类型。 (具体而言,假设xString。那么您建议x 的类型为String(由于参数的模式匹配)和[String](根据x 的使用方式在递归调用中)。

      我不太清楚您要做什么,但是由于x!!n + ...,您在同一行中也有问题。在这里,x!!n(可能)是一个Char,但您使用的是+ 运算符,它没有为Char 定义。您可能指的是 ++ 用于列表追加,除了 x!!n 不是 列表,它是单个元素。因此,您可能是说

      [x!!n] ++ cpfx' ...
      

      (x!!n) : cpfx' ...
      

      【讨论】:

        【解决方案4】:

        这是思考问题的另一种方式:

        假设你有一个最长公共前缀的函数:

        lcp :: String -> String -> String
        

        您可以将其扩展到这样的列表:

        cpfx [a]       = a
        cpfx [a,b]     = lcp a b
        cpfx [a,b,c]   = lcp (lcp a b) c
        cpfx [a,b,c,d] = lcp (lcp (lcp a b) c) d
        ...
        

        这种通用递归模式称为折叠。

        【讨论】:

          【解决方案5】:

          一个简单的函数怎么样:

          import Data.List
          
          cpfx xs = comp (reverse $ minimum xs) (map reverse xs)
           where comp ys xs
              | True == (all (==True) $ map (\x->isSuffixOf ys x) xs)
                            = reverse ys
              | ys  == []   = []
              | otherwise   = comp (tail ys) xs
          

          ...它工作正常:codepad.org

          【讨论】:

            猜你喜欢
            • 2021-09-11
            • 1970-01-01
            • 2022-11-22
            • 2021-10-12
            • 1970-01-01
            • 2020-07-05
            • 2018-09-30
            • 2013-04-14
            • 2012-02-01
            相关资源
            最近更新 更多