Haskell 获取常量长度在函数式编程上下文中使用不可变列表检索时间常量

Haskell 获取常量长度在函数式编程上下文中使用不可变列表检索时间常量,haskell,f#,functional-programming,ocaml,immutability,Haskell,F#,Functional Programming,Ocaml,Immutability,我目前面临的问题是必须根据给定列表的长度进行计算。必须遍历列表的所有元素才能知道它的大小是一个很大的性能损失,因为我使用的是相当大的列表 解决这个问题的建议方法是什么 我想我总是可以将一个大小值与列表一起携带,这样我就可以事先知道它的大小,而不必在调用站点进行计算,但这似乎是一种脆弱的方法。我还可以定义自己的列表类型,其中每个节点都有其属性列表的大小,但这样我就失去了编程语言库为标准列表提供的优势 你们在日常生活中是怎么处理的 我目前正在使用F#。我知道我可以使用.NET的可变(数组)列表,这将

我目前面临的问题是必须根据给定列表的长度进行计算。必须遍历列表的所有元素才能知道它的大小是一个很大的性能损失,因为我使用的是相当大的列表

解决这个问题的建议方法是什么

我想我总是可以将一个大小值与列表一起携带,这样我就可以事先知道它的大小,而不必在调用站点进行计算,但这似乎是一种脆弱的方法。我还可以定义自己的列表类型,其中每个节点都有其属性列表的大小,但这样我就失去了编程语言库为标准列表提供的优势

你们在日常生活中是怎么处理的


我目前正在使用F#。我知道我可以使用.NET的可变(数组)列表,这将解决这个问题。不过,我对纯粹不变的函数方法更感兴趣。

我不明白为什么将长度旋转是一种脆弱的方法。试试这样(哈斯克尔):

等等。如果这是在库或模块中,您可以通过不导出构造函数
NList
使其安全(我认为)

也可以强制GHC记忆
长度
,但我不确定如何记忆或何时记忆。

在F#中,大多数
列表
函数都具有等效的
Seq
函数。这意味着,您可以实现自己的不可变链表,该链表携带每个节点的长度。大概是这样的:

使用
成员this.GetEnumerator()=
(this.sequence).GetEnumerator()
成员this.GetEnumerator()=
(this.sequence:>System.Collections.IEnumerable).GetEnumerator()
模块MyList=
让记录列表=
匹配列表
|[]->MyList无
|head::tail->MyList(部分(head,of list tail))
内置的F#list类型没有任何长度缓存,也没有办法以某种巧妙的方式添加,因此您需要定义自己的类型。我认为为现有的F#
列表
类型编写包装器可能是最好的选择

这样,您可以避免显式转换—当您包装列表时,它实际上不会复制它(如svick的实现中所述),但包装器可以轻松地缓存
Length
属性:

opensystem.Collections
类型(长度列表)=
让length=lazy list.length
成员x.长度=长度.值
成员x.列表=列表
接口IEnumerable with
成员x.GetEnumerator()=(列表:>IEnumerable).GetEnumerator()
接口seq).GetEnumerator()
[]
模块长度列表=
let of List l=长度列表(l)
let of Seq s=长度列表(List.of Seq s)
让toList(l:长度列表)=l.列表
让长度(l:长度列表)=l.长度
使用包装器的最佳方法是使用
LengthList.ofList
从标准F#list创建
LengthList
,并在使用标准
list
模块中的任何函数之前使用
LengthList.toList
(或仅使用
list
)属性

但是,这取决于代码的复杂性——如果只需要几个位置的长度,那么单独保存它并使用tuple
列表可能会更容易
你们在日常生活中是怎么处理的

我们没有,因为这不是日常生活中的问题。这听起来像是一个问题,可能在有限的领域

如果您最近创建了列表,那么您可能已经完成了O(N)项工作,因此遍历列表以获得其长度可能不是什么大问题

如果你制作了一些非常大的列表,但没有太多“改变”(显然从未改变,但我的意思是改变对域/算法中使用的列表头的引用集),那么在引用列表头*长度元组的一侧放一个字典可能是有意义的,当询问长度时,请查阅字典(在需要时做实际的工作来遍历它们,但为将来缓存结果时会询问相同的列表)

最后,如果您真的要处理一些算法,需要不断地更新正在使用的列表并不断地查询长度,那么创建您自己的类似列表的数据类型(是的,您还需要编写map/filter和其他任何代码)


(通常情况下,我认为99.99%的时间最好使用内置数据结构。在0.01%的时间里,如果您正在开发需要高度优化的算法或代码,那么几乎总是需要放弃内置数据结构(对于大多数情况来说,这已经足够好了)并使用定制的数据结构来解决您正在处理的确切问题。在这种情况下,查看wikipedia或Okasaki的“纯功能数据结构”以获取想法和建议。但很少使用这种情况。)

Hmmm。。。我猜列表不是这个案例的正确数据结构。对于某些有限的值集,列表是可以的,但如果这些值的计数变得越来越大,您将陷入性能问题。问题是,如果您参考长度,列表不再流。@fuzzxl我不确定流是什么意思;显然,您不能将其用于无限列表,但也不能在无限列表上运行
length
。我认为这与
NList
不是递归定义的,而常规Haskell列表是递归定义的这一事实有关。对于常规列表,
tail
只需解构cons单元格,而对于
nTail
,则需要解构它并重新构建另一个
NList
data NList a = NList Int [a]

nNil :: NList [a]
nNil = NList 0 []

nCons :: a -> NList a -> NList a
nCons x (NList n xs) = NList (n+1) (x:xs)

nHead :: NList a -> a
nHead (NList _ (x:_)) = x

nTail :: NList a -> NList a
nTail (NList n (_:xs)) = NList (n-1) xs

convert :: [a] -> NList a
convert xs = NList (length xs) xs