我正在做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代码中的常见模式,则引用从函数调用返回的列表的头部和尾部。是否有多个电话打包'在递归的任何级别,这是一个快速的解决方案吗?
答案 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
等模式的where
或h_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
所以你看,在这里使用:
运算符只会通过添加无用的实体来混乱代码。
至于递归的数量,我在这里只能看到每个级别的一个递归调用,所以基本上它是线性运行时间,因此是有效的。