Haskell中最长的公共前缀

时间:2014-02-12 02:54:26

标签: list haskell prefix

我正在尝试根据最长的公共前缀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)行有关。

我可以做些什么来纠正这种情况?

5 个答案:

答案 0 :(得分:4)

如何解决这些错误

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

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

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

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

  | ifMatch (x:t) n = x!!n + cpfx' x (n+1)
 Couldn't match expected type `[Char]' with actual type `Char'
    Expected type: [[Char]]
      Actual type: [Char]
    In the first argument of `(!!)', namely `x'
    In the first argument of `(+)', namely `x !! n'
    No instance for (Num [Char])
      arising from a use of `+'

ifMatch中的一个:

  | x!!n == x!!n = ifMatch xs n
    Couldn't match expected type `[Char]' with actual type `Char'
    Expected type: [[Char]]
      Actual type: [Char]
    In the first argument of `ifMatch', namely `xs'
    In the expression: ifMatch xs n

现在,cpfx'中的错误非常简单:x[Char]x !! nChar,并希望将其纳入列表,因此请使用:代替+。此外,您要将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

使用fold

的简单方法

让我们的函数变得更简单,但通过为实现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" 。现在,某些单词的公共前缀可以写成:

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

现在问题是,如果你想为一系列事情做点什么,你通常会使用fold

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

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

\

最后一个示例与line before! However, we do not have a starting value, so we would use the first element of our list instead. Luckily, there's already [ commonPrefix` commonPrefixAll :: (Eq a) => [[a]] -> [a] commonPrefixAll = foldl1 commonPrefix foldl1`] 2完全相同,就是这样。因此,我们之前复杂的功能归结为:

{{1}}

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

答案 1 :(得分: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"]

答案 2 :(得分:0)

你提到的那条线确实是个问题。

请注意,cpfx'的参数为(x:[]),即所有类型与x类型相同的列表,但您的递归调用仅使用x 。因此,您会收到无限类型错误:您正在尝试识别类型为x的{​​{1}}类型。 (具体而言,假设[x]x。那么您建议String具有类型x(由于参数的模式匹配)和{{1 (通过在递归调用中使用String的方式)。

我不太清楚你要做什么,但由于[String],你在同一行也有问题。在这里,x(可能)是x!!n + ...,但您使用的是x!!n运算符,该运算符未针对Char定义。对于列表追加,您可能需要+,除了Char 列表,它是单个元素。因此你可能意味着

++

x!!n

答案 3 :(得分:0)

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

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

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
...

这种一般的递归模式称为折叠。

答案 4 :(得分:0)

如下的简单功能怎么样:

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