cons运算符(::)在F#

F#中的::运算符总是将元素前置到列表中。 有附加到列表中的运算符吗? 我猜测使用@运算符

[1; 2; 3] @ [4]

效率会比追加一个元素低。

正如其他人所说,没有这样的运营者,因为这没有多大意义。 我其实觉得这是一件好事,因为这样可以更容易地认识到操作效率不高。 在实践中,你不应该需要操作员 – 通常有更好的方法来写同样的事情。

典型的场景:我认为,你可能认为你需要将元素添加到最后的典型场景非常普遍,以至于描述它可能是有用的。

当您使用累加器参数编写函数的尾recursion版本时,添加元素似乎是必要的。 例如,列表的filter函数(效率低下)的实现如下所示:

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

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

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

在(2)中,我们现在将元素添加到累加器的前面,并且当函数要返回结果时,我们反转列表(1),比单独添加元素效率更高。

F#中的列表是单链接和不可变的。 这意味着对前面的O(1)(创build一个元素,并指向一个现有的列表),而后面的嗅觉是O(N)(因为整个列表必须被复制;你不能改变现有的最终指针,你必须创build一个全新的列表)。

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

 l @ [42] 

是这样做的方式,但这是一种代码味道。

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

 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 [] 

这个想法是,你在function上将一个列表作为一个从“其余元素”到“最终列表”的函数来表示。 如果你打算在查看任何元素之前build立整个列表,这个工作就产生了。 否则,你将不得不处理追加的线性成本或完全使用另一个数据结构。

我猜测使用@操作符会比追加一个元素效率低。

如果是的话,这是一个可以忽略的差异。 追加单个项目并将列表连接到最后是O(n)操作。 事实上,我想不出一件@必须做的事情,单项追加function不会。

也许你想使用另一个数据结构。 我们在fsharpx中有双端队列(或简称“Deques”)。 你可以在http://jackfoxy.com/double-ended-queues-for-fsharp上阅读更多关于它们的信息;

效率(或缺乏)来自遍历列表以find最终的元素。 所以用[4]来声明一个新的列表对于除了最微不足道的场景之外的所有其他的都是微不足道的。

尝试使用双端队列而不是列表。 我最近添加了4个版本的deques(Okasaki的拼写)到FSharpx.Core(可通过NuGet。在FSharpx.Core.Datastructures的源代码)。 看看我的关于使用dequeus的文章双重队列的F#

我向F#团队build议了Cons运算符::,并且使用主动模式鉴别符可以用于具有头部/尾部签名的其他数据结构。 3