Haskell卫兵如何评估?

时间:2014-12-22 19:27:10

标签: haskell

我正在做99个Haskell问题,在其中一个解决方案中,我遇到了以下代码:

pack' [] = []
pack' [x] = [[x]]
pack' (x:xs)
    | x == head  h_p_xs = (x:h_p_xs):t_p_hs
    | otherwise         = [x]:p_xs
    where p_xs@(h_p_xs:t_p_hs) = pack' xs

我想知道什么时候打包'在第一个守卫中被调用,如果这是Haskell代码中的常见模式,则引用从函数调用返回的列表的头部和尾部。是否有多个电话打包'在递归的任何级别,这是一个快速的解决方案吗?

3 个答案:

答案 0 :(得分:2)

<强>包&#39;似乎像 Data.List.group 一样工作 - 它将连续的相等元素分组到一个列表中。所以,打包&#39; [1,1,3,2,2]应返回[[1,1],[3],[2,2]]。

模式匹配是惯用的haskell。在这里,在声明中,

h_p_xs:t_p_hs = pack' hs

我们知道包装&#39; hs返回一个列表。因此,h_p_xs与其头部匹配,而t_p_hs与其尾部匹配。 LHS和RHS都必须具有相同的结构(这里双方都有列表结构),以便在Haskell中使用模式匹配。 p_xs在这里匹配整个RHS(@ pattern)。所以,是的,这是Haskell中非常常见的习语。

in pack&#39;定义,前两行处理空单和单例列表。因此,只有当输入列表具有多个元素且其第一个和第二个元素相等时,第一个保护条件才适用。

在任何级别,最多只有一次递归调用打包,因此时间复杂度为列表长度的O(n)。应该很快。

答案 1 :(得分:2)

  

我想知道什么时候打包'在第一个后卫中被召唤

后卫x == head h_p_xs强制对h_p_xs进行评估,从而触发递归调用。

  

如果是这样的话   Haskell代码中的一个常见模式,用于引用头部和尾部   从函数调用返回的列表。

我认为这是一种非常常见的模式。您也可以使用case pack' xs of ...let ... = pack' xs in ...找到变体。

请注意,只要找到空列表,使用带有let等模式的whereh_p_xs:t_p_xs就会导致运行时错误。此代码小心确保递归调用不会返回emlty列表。

  

是否有多次打电话'   在任何递归级别

为了迂腐,Haskell标准没有规定如何实际评估代码,而只是指定结果。因此,理论上,允许编译器进行任意数量的递归调用。

实际上,编译器会小心地只进行一次递归调用 - 不这样做会导致可怕的性能。

为了便于比较,下面的代码是等效的,但会导致指数复杂度(!)

...
where p_xs = h_p_xs:t_p_hs
      h_p_xs = head (pack' xs)
      t_p_xs = tail (pack' xs)

在这里,您可以期望编译器进行两次递归调用。

  

这是一个快速的解决方案吗?

是。预计在输入上以线性时间运行。

答案 2 :(得分:1)

这是引用head的常用方法,如果它使代码更具可读性。当然,您可以通过在最后一行上执行此操作,例如

p_xs@(  (h_p_x : h_p_xs)  : t_p_hs) = pack' xs

而不是

p_xs@( h_p_xs : t_p_hs ) = pack' xs

这样你就可以在h_p_x变量中获得头部。但是你需要在第四行添加:

| x == head  h_p_xs = (x : h_p_x: h_p_xs):t_p_hs

所以你看,在这里使用:运算符只会通过添加无用的实体来混乱代码。 至于递归的数量,我在这里只能看到每个级别的一个递归调用,所以基本上它是线性运行时间,因此是有效的。