【问题标题】:head and tail on difference lists in Haskell, à la LYAHHaskell,à la LYAH中差异列表上的头部和尾部
【发布时间】:2012-01-09 18:55:46
【问题描述】:

Learn You a Haskell 提到 difference lists(在该页面上搜索该术语),其中列表 l 不是直接表示,而是作为函数 (l++) 表示。这允许更有效的左右连接。串联成为函数组合,最终可以通过($[])转换成真实的列表。我想知道可以在差异列表上有效地支持哪些操作。例如,对于差异列表,(:) 的等价物是

\x l -> (x:) . l

可以有效地为差异列表实现headtail 吗?这是明显的实现:

headTailDifList :: ([a] -> [a]) -> (a, [a] -> [a])
headTailDifList dl = (head l, ((tail l)++))
    where
    l = dl []

对于真实列表,\l -> (head l, tail l) 以恒定时间运行。这个headTailDifList 怎么样?可能是因为惰性求值,只有l 的第一个元素会被求值?

  1. 考虑到差异列表是一个函数而不是实际的“值”,问这是否在恒定时间内运行意味着什么?
  2. headTailDifList 是否以恒定时间运行?
  3. 还有其他一些固定时间的实现吗?这是一个候选人:

    headTailDifList dl = (head (dl []), tail.dl )
    

    但是,如果dlid(空差异列表),则尾部不会引发异常。

【问题讨论】:

  • 可以在 dlist 上实现 head 和 tail,不应该。 Dlists 仅适用于构建常规 cons 列表后的高效构建和简单的“变质”。如果您需要像 head 或 tail 这样的操作,您需要不同的数据结构,例如Data.Sequence 或连接(二进制)列表。

标签: list haskell lazy-evaluation


【解决方案1】:

查看dlist 包,它提供了差异列表的典型实现。它定义了一个类型DList

newtype DList a = DL { unDL :: [a] -> [a] }

以及所有常用的列表函数,包括headtail

head :: DList a -> a
head = list (error "Data.DList.head: empty list") const

tail :: DList a -> DList a
tail = list (error "Data.DList.tail: empty list") (flip const)

这里,list 定义为:

list :: b -> (a -> DList a -> b) -> DList a -> b
list nill consit dl =
  case toList dl of
    [] -> nill
    (x : xs) -> consit x (fromList xs)

与:

fromList    :: [a] -> DList a
fromList    = DL . (++)

toList      :: DList a -> [a]
toList      = ($[]) . unDL

也就是说,list 需要 O(n) 时间来生成所有元素。正如 JohnL 所指出的,只是生成第一个元素可能会在恒定时间内完成。

【讨论】:

  • 不,正如 John L 的回答所说,toList 是由于懒惰而导致的恒定时间。在toList 之后检查整个列表需要线性时间。
【解决方案2】:

编辑:添加了更多关于 thunk 的信息。

首先查看从差异列表到常规列表的转换。仅此操作仅需要恒定时间,因为不需要评估。结果列表将作为 thunk 存在,其结构如下:

(++) 的基本定义是右关联的,需要遍历整个第一个参数才能继续第二个参数。这与差异列表生成的嵌套结构完美匹配,因为每个 (++) 都将单个列表块作为其第一个参数。此外,因为(++) 是一个很好的列表生成器,所以整个结构是惰性存在的。尽管完全评估它需要 O(n),但仅评估头部是 O(k),其中 k=number of chunks

考虑a,b == []; c = [1..]。然后head 需要先检查ab 是否为空,然后再转到c 并找到第一个元素。在最坏的情况下,head 会在找到空列表构造函数之前遍历整个结构。然而实际上这是一种非常罕见的情况,即使这样也不是特别有害,因为遇到空块并继续前进是一个恒定时间的操作。

在一般情况下,评估差异列表应该在时间复杂度上与常规列表的差异仅在等效操作的常数因子上。

仅生成差异列表的第一个元素不需要 O(n) 时间,这可以通过使用无限列表轻松演示:

*Dl Control.Monad Control.Applicative> head $ ($ []) $ toDl [1..] . toDl [4..]
1
(0.01 secs, 2110104 bytes)

*Dl Control.Monad Control.Applicative> fmap (head . ($ [])) $ headTailDifList (toDl [1..])
(1,2)
(0.00 secs, 2111064 bytes)

*Dl Control.Monad Control.Applicative> fmap (head . ($ [])) $ headTailDifList (toDl [1..] . toDl [3..] . toDl [] . toDl [5..])
(1,2)
(0.01 secs, 3105792 bytes)

-- create a difference list
toDl :: [a] -> ([a] -> [a])
toDl l = (l ++)

【讨论】:

    猜你喜欢
    • 2013-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多