在F#中的cons运算符(::)

时间:2010-03-20 13:15:29

标签: f# functional-programming

F#中的::运算符始终将元素添加到列表中。是否有运营商附加到列表中?我猜这是使用@ operator

[1; 2; 3] @ [4]

效率低于附加一个元素。

7 个答案:

答案 0 :(得分:43)

正如其他人所说,没有这样的运营商,因为它没有多大意义。我实际上认为这是一件好事,因为它使人们更容易意识到操作效率不高。在实践中,您不应该需要操作员 - 通常有更好的方法来编写相同的东西。

典型场景:我认为您可能认为需要将元素追加到最后的典型场景非常常见,因此描述它可能很有用。

当您使用 accumulator参数编写函数的尾递归版本时,添加元素似乎是必要的。例如,列表的filter函数的(低效)实现看起来像这样:

let filter f l = 
  let rec filterUtil acc l =
    match l with 
    | [] -> acc
    | x::xs when f x -> filterUtil (acc @ [x]) xs
    | x::xs -> filterUtil acc xs
  filterUtil [] l

在每一步中,我们需要将一个元素附加到累加器(它存储要作为结果返回的元素)。可以轻松修改此代码以使用::运算符,而不是将元素附加到acc列表的末尾:

let filter f l = 
  let rec filterUtil acc l =
    match l with 
    | [] -> List.rev acc                        // (1)
    | x::xs when f x -> filterUtil (x::acc) xs  // (2)
    | x::xs -> filterUtil acc xs
  filterUtil [] l

在(2)中,我们现在将元素添加到累加器的前面,当函数即将返回结果时,我们反转列表(1),这比通过附加元素一样有效得多之一。

答案 1 :(得分:25)

F#中的列表是单链接且不可变的。这意味着在前面进行的是O(1)(创建一个元素并让它指向现有列表),而后面的snocing是O(N)(因为必须复制整个列表;你不能改变它现有的最终指针,你必须创建一个全新的列表。)

如果你确实需要“向后面追加一个元素”,那么就像

l @ [42]

是这样做的方法,但这是代码味道。

答案 2 :(得分:12)

附加两个标准列表的成本与左侧列表的长度成比例。特别是

的成本
xs @ [x]

xs长度成正比 - 是是固定成本。

如果你想要一个带有常量时间追加的类似列表的抽象,你可以使用John Hughes的函数表示,我称之为hlist。我将尝试使用OCaml语法,我希望它足够接近F#:

type 'a hlist = 'a list -> 'a list   (* a John Hughes list *)
let empty : 'a hlist = let id xs = xs in id
let append xs ys = fun tail -> xs (ys tail)
let singleton x = fun tail -> x :: tail
let cons x xs = append (singleton x) xs
let snoc xs x = append xs (singleton x)
let to_list : 'a hlist -> 'a list = fun xs -> xs []

这个想法是你在功能上将一个列表表示为从“其余元素”到“最终列表”的函数。如果要在查看任何元素之前构建整个列表,这将创建。否则,您将不得不处理线性成本的追加或完全使用其他数据结构。

答案 3 :(得分:5)

  

我猜测使用@ operator [...]效率低于附加一个元素。

如果是,那将是一个微不足道的差异。附加单个项目并将列表连接到末尾都是O(n)个操作。事实上,我无法想到@必须做的一件事,单项追加函数不会。

答案 4 :(得分:3)

也许您想使用其他数据结构。我们在fsharpx中有双端队列(或简称“Deques”)。您可以在http://jackfoxy.com/double-ended-queues-for-fsharp

了解更多相关信息

答案 5 :(得分:1)

效率(或缺乏)来自于遍历列表以找到最终元素。因此,除了最微不足道的场景之外,使用[4]声明一个新列表几乎可以忽略不计。

答案 6 :(得分:0)

尝试使用双端队列而不是列表。我最近在FSharpx.Core中添加了4个版本的deques(Okasaki的拼写)(可通过NuGet获得。源代码位于FSharpx.Core.Datastructures)。请参阅我关于使用dequeus Double-ended queues for F#

的文章

我已经向F#团队建议了cons运算符::,并且活动模式鉴别器可用于具有头/尾签名的其他数据结构。3