Elem如何使用单个元素

时间:2016-11-28 07:15:28

标签: haskell

问题是将连续重复的列表元素打包到子列表中。 我不明白elem如何处理单个元素,

例如,

pack [1,1,2,2,3,4]

然后x将为1,(head (pack xs))将为1。 怎么能:

1 `elem`  1

elem类型a -> [a] -> Bool时的工作?

另外,请解释一下递归。

pack :: (Eq a) => [a] -> [[a]]
pack [] = []
pack [x] = [[x]]
pack (x:xs) = if x `elem` (head (pack xs))
              then (x:(head (pack xs))):(tail (pack xs))
              else [x]:(pack xs)

2 个答案:

答案 0 :(得分:7)

在您的示例中,x将为1,但head (pack xs) 不是 1.实际上,您可以从其类型签名中看到{{1}的类型对于某种数字pack xs,必须是[[Int]](或至少[[a]]),即数字列表列表a类型为head (pack xs),而不是[Int]类型。无论它的价值是什么,它都不能是Int

如果仍然不清楚,请查看较短​​的示例1。它会将模式pack [1,2]pack (x:xs)x=1匹配,因此xs=[2]调用的右侧将为elem。您可以验证这相当于评估为head (pack [2])的{​​{1}},现在是以下表达式:

head [[2]]

很有道理。

请记住,即使[2]返回列表的第一个元素,Haskell也允许列表列表,列表列表的第一个元素是另一个列表。

编辑:要解释递归,让我们详细了解1 `elem` [2] 的完整示例。每当我们尝试评估head来电时,我们都会使用pack [1,1,2]中我编号的其中一种模式:

pack

当Haskell尝试评估pack时,因为pack [] = [] -- #1 pack [x] = [[x]] -- #2 pack (x:xs) = if x `elem` (head (pack xs)) -- #3 then (x:(head (pack xs))):(tail (pack xs)) else [x]:(pack xs) 等同于pack [1,1,2](如果您不明白为什么,请查看[1,1,2]运算符的定义,试试看几个例子!),它将模式#3与1:[1,2]:相匹配。因此,Haskell可以使用模式#3的右侧对x=1xs=[1,2]的这些替换值进行评估。为了完全清楚,具有这些替换的RHS看起来如下,我们将其称为“表达式(A)”:

x

在继续之前,请确保您认为这是正确的!现在,评估这个表达式最困难的部分是弄清楚如何评估xs,它在几个地方使用。那么,让我们弄清楚Haskell如何评估if 1 `elem` (head (pack [1,2])) then (1:(head (pack [1,2]))):(tail (pack [1,2])) else [1]:(pack [1,2]) 。同样,因为pack [1,2]相当于pack [1,2](检查它!),所以这会将模式#3与[1,2]1:[2]相匹配。具有这些替换的模式#3的RHS如下,我们将其称为“表达式(B)”:

x=1

要评估表达式(B),我们需要计算出xs=[2]的值。这次,该表达式将模式#2与if 1 `elem` (head (pack [2])) then (1:(head (pack [2]))):(tail (pack [2])) else [1]:(pack [2]) 匹配。 (实际上,它也会与模式#3匹配pack [2]x=2,但Haskell使用匹配的第一个模式,因此它从不考虑这种可能性。)将x=2替换为RHS对于模式#2,我们得到xs=[]的以下等价值,我们将其称为“表达式(C)”,即使它太短也可能不值得一个名字。

x=2

考虑到这一点并将其替换回上面的表达式(B),我们实际得到:

pack [2]

我在这里完成的所有操作都会被[[2]] 替换为if 1 `elem` (head [[2]]) then (1:(head [[2]])):(tail [[2]]) else [1]:[[2]] ,但是我删除了一些没有改变含义的额外括号。 pack [2]语句中的条件与[[2]]相同,后者为false,因此值为if,可以将其重写为1 `elem` [2]。 (再次,检查你是否不明白为什么。)这是表达式(B)的最终值,因此是[1]:[[2]]的最终值。现在,我们可以将此值替换为表达式(A)以获取:

[[1],[2]]

现在,因为pack [1,2]只是if 1 `elem` (head [[1],[2]]) then (1:(head [[1],[2]])):(tail [[1],[2]]) else [1]:[[1],[2]] head [[1],[2]]语句中的条件是[1],这是真的,所以Haskell会评估{{1}给出的部分子句。这很难看,但你应该能够说服自己,它的价值在于:

if

这是表达式(A)的最终值,但当然这是1 `elem` [1]的值,所以你可以看到它已经完成了。

答案 1 :(得分:1)

正如chi已经评论过的那样,使用headtail通常会产生问题并且模糊不清。在您的示例中,它还会产生巨大的性能问题,因为在每个递归步骤中,您反复评估pack xs - 除非编译器介入一些非平凡的优化,否则这具有指数复杂性!

我建议使用case和警卫:

以下内容
pack (x:xs) = case pack xs of
   thisPack : packs
     | x `elem` thisPack -> (x:thisPack) : packs
   otherPacks            -> [x] : otherPacks