如何在不使用引用的情况下删除F#序列中的重复项

如何在不使用引用的情况下删除F#序列中的重复项,f#,F#,我有一个已排序的序列,希望遍历它并返回序列中唯一的条目。我可以使用下面的函数,但是它使用了参考变量,我认为这不是解决问题的正确方法 let takeFirstCell sectors = let currentRNCId = ref -1 let currentCellId = ref -1 seq { for sector in sectors do if sector.RNCId

我有一个已排序的序列,希望遍历它并返回序列中唯一的条目。我可以使用下面的函数,但是它使用了参考变量,我认为这不是解决问题的正确方法

    let takeFirstCell sectors = 
        let currentRNCId = ref -1
        let currentCellId = ref -1
        seq {
            for sector in sectors do
                if sector.RNCId <> !currentRNCId || sector.CellId <> !currentCellId then
                    currentRNCId := sector.RNCId
                    currentCellId := sector.CellId
                    yield sector
        }
let takeFirstCell扇区=
设currentRNCId=ref-1
设currentCellId=ref-1
序号{
为部门中的部门做准备
如果sector.RNCId!currentRNCId | | sector.CellId!currentCellId,则
currentRNCId:=扇区.RNCId
currentCellId:=扇区.CellId
产量部门
}

如何以功能性的方式进行此操作?

只需按如下顺序初始化一个唯一的集合(如集合):

set [1; 2; 3; 3; 4; 5; 5];;
=> val it : Set<int> = set [1; 2; 3; 4; 5]
设置[1;2;3;3;4;5];;
=>valit:Set=Set[1;2;3;4;5]

Seq.distinct(1::[1..5])
返回
Seq[1;2;3;4;5]
。这就是你的意思吗?

distinct
distinctBy
都使用
Dictionary
,因此需要散列和一点内存来存储唯一的项。如果序列已排序,则可以使用以下方法(与您的方法类似)。它的速度几乎是原来的两倍,而且内存的使用也很稳定,可以用于任何大小的序列

[1;1;1;2;2;2;3;3;3]
|> Seq.distinctBy id
|> printfn "%A"
let distinctWithoutHash (items:seq<_>) =
  seq {
    use e = items.GetEnumerator()
    if e.MoveNext() then
      let prev = ref e.Current
      yield !prev
      while e.MoveNext() do
        if e.Current <> !prev then 
          yield e.Current
          prev := e.Current
  }

let items = Seq.init 1000000 (fun i -> i / 2)
let test f = items |> f |> (Seq.length >> printfn "%d")

test Seq.distinct        //Real: 00:00:01.038, CPU: 00:00:01.435, GC gen0: 47, gen1: 1, gen2: 1
test distinctWithoutHash //Real: 00:00:00.622, CPU: 00:00:00.624, GC gen0: 44, gen1: 0, gen2: 0
let distinctWithoutHash(项目:seq)=
序号{
使用e=items.GetEnumerator()
如果e.MoveNext()那么
设prev=参考e.电流
屈服
而e.MoveNext()做什么
如果e.当前!上一个,则
输出电流
上一个:=e.当前值
}
let items=Seq.init 1000000(乐趣i->i/2)
让测试f=项目|>f |>(Seq.length>>printfn“%d”)
测试顺序不同//Real:00:00:01.038,CPU:00:00:01.435,GC gen0:47,gen1:1,gen2:1
测试distinctWithoutHash//Real:00:00.622,CPU:00:00:00.624,GC gen0:44,gen1:0,gen2:0

我想不出用
mutable
s代替
ref
s的方法,我确信这会大大加快运算速度(我试过了,没什么区别)。

下面的解决方案保留了元素的顺序,只返回泛型列表中元素的第一次出现。当然,这会生成一个新列表,其中删除了冗余项

//  ****  Returns a list having subsequent redundant elements removed
let removeDuplicates(lst : 'a list) = 
    let f item acc =
        match acc with 
        | [] -> [item]
        | _ ->
            match List.exists(fun x -> x = item) acc with
            | false -> item :: acc
            | true -> acc
    lst 
    |> List.rev
    |> fun x -> List.foldBack f x []
    |> List.rev
//  **** END OF FUNCTION removeDuplicates

val removeDuplicates : 'a list -> 'a list when 'a : equality
val testList : int list = [1; 4; 3; 1; 2; 2; 1; 1; 3; 4; 3]
val tryAbove : int list = [1; 4; 3; 2]

在我的例子中,我不能使用Seq.distinct,因为我需要保持列表元素的顺序。 我用的是我的解决方案。 我认为它很短

let rec compress = function
    | a :: (b :: _ as t) -> if a = b then compress t else a :: compress t
    | smaller -> smaller

不允许可变表脱离其环境(放在堆上由闭包引用),并且由于像
seq
(好吧,
seq
得到一些特殊的编译器优化)这样的计算表达式将其分解为一个连续链,因此必须使用
ref
(F#设计者希望我们能够将可变内容作为纯粹的本地内容进行推理)……因此我认为,即使允许可变内容脱离其环境,它们也可能不会提高性能,因为它们可能会被分配到堆上(尽管另一种可能的实现只是在返回后保持stackframe的活动性).噢,关于你的
与uthash
实现的区别,另一个好处是它很懒惰!@Stephen:我写了一个
iUnumerator
,它使用了
可变的
,但我不想麻烦发布它——性能(令人惊讶地)几乎相同。不要匹配真/假,如果需要就用