According to the .NET documentation, HashSet<T>
does not maintain order when enumerated so I implemented my own using F#.
Can anyone help review if this is threadsafe and correct?
open System open System.Collections open System.Collections.Generic type LinkedHashSet<'a when 'a : equality> () = let mutable index = 0 let indexed = SortedDictionary<int, 'a> () let hash = HashSet<'a> () let theLock = obj () interface ICollection<'a> with member __.Count = lock theLock (fun () -> hash.Count) member __.IsReadOnly = false member __.Add e = lock theLock (fun () -> if hash.Add e then indexed.[index] <- e index <- index + 1 ) member __.Clear () = lock theLock (fun () -> index <- 0 indexed.Clear () hash.Clear () ) member __.Contains e = lock theLock (fun () -> hash.Contains e) member __.CopyTo (array, arrayIndex) = lock theLock (fun () -> let kvps = Seq.toArray indexed let kvpLength = Array.length kvps for i in arrayIndex..kvpLength - 1 do array.[i - arrayIndex] <- kvps.[i].Value ) member __.GetEnumerator () = let values = lock theLock (fun () -> indexed |> Seq.map (fun kvp -> kvp.Value) ) values.GetEnumerator () member this.GetEnumerator () : IEnumerator = (this :> IEnumerable).GetEnumerator () member __.Remove e = let matcher (kvp: KeyValuePair<int, 'a>) = kvp.Value = e lock theLock (fun () -> if hash.Remove e then let kvps = indexed :> seq<KeyValuePair<int, 'a>> match Seq.tryFind matcher kvps with | Some x -> indexed.Remove x.Key | None -> false else false )
Usage (distinctChar
takes a string aabbb
and returns ab
):
let distinctChars (s: string) = s.ToCharArray () |> Array.fold (fun (set: ICollection<char>) e -> set.Add e; set) (LinkedHashSet () :> ICollection<char>) |> Seq.toArray |> String