Skip to content

Commit

Permalink
Cached List.GetHashCode
Browse files Browse the repository at this point in the history
  • Loading branch information
ncave committed Jul 24, 2020
1 parent 601026c commit b9b63a5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 56 deletions.
2 changes: 2 additions & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,8 @@ let identityHash r (arg: Expr) =
Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r)
| DeclaredType(ent,_) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType ->
Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r)
| DeclaredType(ent,_) ->
Helper.InstanceCall(arg, "GetHashCode", Number Int32, [], ?loc=r)
| _ ->
Helper.CoreCall("Util", "identityHash", Number Int32, [arg], ?loc=r)

Expand Down
124 changes: 68 additions & 56 deletions src/fable-library/List.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,72 @@ open Fable.Core
let msgListWasEmpty = "List was empty"
let msgListNoMatch = "List did not contain any matching elements"

[<CustomEquality; CustomComparison>]
type List<'T> when 'T : comparison =
{ Count: int; Values: ResizeArray<'T> }
type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) =
let mutable hashCode = None

static member Empty =
{ Count = 0; Values = ResizeArray<'T>() }
static member Cons (x: 'T, xs: 'T list) =
static member Empty = new List<'T>(0, ResizeArray<'T>())
static member Cons (x: 'T, xs: 'T list) = xs.Add(x)

member _.Add(x: 'T) =
let values =
if xs.Count = xs.Values.Count
then xs.Values
else xs.Values.GetRange(0, xs.Count)
if Count = Values.Count
then Values
else Values.GetRange(0, Count)
values.Add(x)
{ Count = values.Count; Values = values }
new List<'T>(values.Count, values)

member _.IsEmpty = Count <= 0
member _.Length = Count

member _.Head =
if Count > 0
then Values.[Count - 1]
else failwith msgListWasEmpty

member _.Tail =
if Count > 0
then new List<'T>(Count - 1, Values)
else failwith msgListWasEmpty

member _.Item with get(index) =
Values.[Count - 1 - index]

override xs.ToString() =
"[" + System.String.Join("; ", xs) + "]"

override xs.GetHashCode() =
let inline combineHash x y = (x <<< 1) + y + 631
let mutable res = 0
for i = xs.Count - 1 downto 0 do
res <- combineHash res (hash xs.Values.[i])
res
override xs.Equals(other: obj) =
let ys = other :?> 'T list
if xs.Length <> ys.Length then false
elif xs.GetHashCode() <> ys.GetHashCode() then false
else Seq.forall2 (Unchecked.equals) xs ys
// (xs :> System.IComparable).CompareTo(other) = 0

override xs.Equals(that: obj) =
(xs :> System.IComparable).CompareTo(that) = 0
override xs.GetHashCode() =
match hashCode with
| Some h -> h
| None ->
let inline combineHash i x y = (x <<< 1) + y + 631 * i
let len = min (xs.Length - 1) 18 // limit the hash count
let mutable h = 0
for i = 0 to len do
h <- combineHash i h (hash xs.[i])
hashCode <- Some h
h

interface System.IComparable with
member xs.CompareTo(that: obj) =
List.CompareWith compare xs (that :?> 'T list)
member xs.CompareTo(other: obj) =
List.CompareWith compare xs (other :?> 'T list)

interface System.Collections.Generic.IEnumerable<'T> with
member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> =
let elems = seq { for i = xs.Count - 1 downto 0 do yield xs.Values.[i] }
let len = xs.Length - 1
let elems = seq { for i = 0 to len do yield xs.[i] }
elems.GetEnumerator()

interface System.Collections.IEnumerable with
member this.GetEnumerator(): System.Collections.IEnumerator =
((this :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() :> System.Collections.IEnumerator)

member xs.Length = xs.Count
member xs.Head =
if xs.Count > 0
then xs.Values.[xs.Count - 1]
else failwith msgListWasEmpty
member xs.Tail =
if xs.Count > 0
then { Count = xs.Count - 1; Values = xs.Values }
else failwith msgListWasEmpty
member xs.IsEmpty = xs.Count <= 0
member xs.Item with get(index) =
xs.Values.[xs.Count - 1 - index]

static member CompareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int =
if obj.ReferenceEquals(xs, ys)
then 0
Expand All @@ -80,9 +94,7 @@ type List<'T> when 'T : comparison =

and 'T list when 'T: comparison = List<'T>

open System.Collections.Generic

let newList (values: ResizeArray<'T>) = { Count = values.Count; Values = values }
let newList (values: ResizeArray<'T>) = new List<'T>(values.Count, values)

let empty () = List.Empty

Expand Down Expand Up @@ -213,7 +225,7 @@ let iterateIndexed f xs =
let iterateIndexed2 f xs ys =
fold2 (fun i x y -> f i x y; i + 1) 0 xs ys |> ignore

let ofArray (xs: IList<'T>) =
let ofArray (xs: System.Collections.Generic.IList<'T>) =
let mutable res = List.Empty
for i = xs.Count - 1 downto 0 do
res <- cons xs.[i] res
Expand Down Expand Up @@ -315,13 +327,13 @@ let choose f xs =
| Some y -> cons y acc
| None -> acc) List.Empty xs |> reverse

let contains (value: 'T) (xs: 'T list) ([<Inject>] eq: IEqualityComparer<'T>) =
let contains (value: 'T) (xs: 'T list) ([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'T>) =
tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome

let except (itemsToExclude: seq<'t>) (xs: 't list) ([<Inject>] eq: IEqualityComparer<'t>): 't list =
let except (itemsToExclude: seq<'t>) (xs: 't list) ([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'t>): 't list =
if isEmpty xs then xs
else
let cached = HashSet(itemsToExclude, eq)
let cached = System.Collections.Generic.HashSet(itemsToExclude, eq)
xs |> filter cached.Add

let initialize n f =
Expand Down Expand Up @@ -373,16 +385,16 @@ let sortWith (comparison: 'T -> 'T -> int) (xs: 'T list): 'T list =
values.Sort(System.Comparison<_>(comparison))
values |> ofSeq

let sort (xs: 'T list) ([<Inject>] comparer: IComparer<'T>): 'T list =
let sort (xs: 'T list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'T>): 'T list =
sortWith (fun x y -> comparer.Compare(x, y)) xs

let sortBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: IComparer<'b>): 'a list =
let sortBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'b>): 'a list =
sortWith (fun x y -> comparer.Compare(projection x, projection y)) xs

let sortDescending (xs: 'T list) ([<Inject>] comparer: IComparer<'T>): 'T list =
let sortDescending (xs: 'T list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'T>): 'T list =
sortWith (fun x y -> comparer.Compare(x, y) * -1) xs

let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: IComparer<'b>): 'a list =
let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'b>): 'a list =
sortWith (fun x y -> comparer.Compare(projection x, projection y) * -1) xs

let sum (xs: 'T list) ([<Inject>] adder: IGenericAdder<'T>): 'T =
Expand All @@ -391,16 +403,16 @@ let sum (xs: 'T list) ([<Inject>] adder: IGenericAdder<'T>): 'T =
let sumBy (f: 'T -> 'T2) (xs: 'T list) ([<Inject>] adder: IGenericAdder<'T2>): 'T2 =
fold (fun acc x -> adder.Add(acc, f x)) (adder.GetZero()) xs

let maxBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: IComparer<'b>): 'a =
let maxBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'b>): 'a =
reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then y else x) xs

let max (li:'a list) ([<Inject>] comparer: IComparer<'a>): 'a =
let max (li:'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'a>): 'a =
reduce (fun x y -> if comparer.Compare(y, x) > 0 then y else x) li

let minBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: IComparer<'b>): 'a =
let minBy (projection: 'a -> 'b) (xs: 'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'b>): 'a =
reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then x else y) xs

let min (xs: 'a list) ([<Inject>] comparer: IComparer<'a>): 'a =
let min (xs: 'a list) ([<Inject>] comparer: System.Collections.Generic.IComparer<'a>): 'a =
reduce (fun x y -> if comparer.Compare(y, x) > 0 then x else y) xs

let average (xs: 'T list) ([<Inject>] averager: IGenericAverager<'T>): 'T =
Expand Down Expand Up @@ -452,11 +464,11 @@ let splitAt i (xs: 'T list) =
if i > xs.Length then invalidArg "index" "The input sequence has an insufficient number of elements."
take i xs, skip i xs

let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([<Inject>] eq: IEqualityComparer<'Key>) =
let hashSet = HashSet<'Key>(eq)
let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'Key>) =
let hashSet = System.Collections.Generic.HashSet<'Key>(eq)
xs |> filter (projection >> hashSet.Add)

let distinct (xs: 'T list) ([<Inject>] eq: IEqualityComparer<'T>) =
let distinct (xs: 'T list) ([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'T>) =
distinctBy id xs eq

let exactlyOne (xs: 'T list) =
Expand All @@ -465,8 +477,8 @@ let exactlyOne (xs: 'T list) =
| 0 -> invalidArg "list" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
| _ -> invalidArg "list" "Input list too long"

let groupBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: IEqualityComparer<'Key>): ('Key * 'T list) list =
let dict = Dictionary<'Key, 'T list>(eq)
let groupBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'Key>): ('Key * 'T list) list =
let dict = System.Collections.Generic.Dictionary<'Key, 'T list>(eq)
let mutable keys = List.Empty
xs |> iterate (fun v ->
let key = projection v
Expand All @@ -480,8 +492,8 @@ let groupBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: IEqualityCompa
keys |> iterate (fun key -> result <- cons (key, reverse dict.[key]) result)
result

let countBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: IEqualityComparer<'Key>) =
let dict = Dictionary<'Key, int>(eq)
let countBy (projection: 'T -> 'Key) (xs: 'T list)([<Inject>] eq: System.Collections.Generic.IEqualityComparer<'Key>) =
let dict = System.Collections.Generic.Dictionary<'Key, int>(eq)
let mutable keys = List.Empty
xs |> iterate (fun v ->
let key = projection v
Expand Down
4 changes: 4 additions & 0 deletions tests/Main/ComparisonTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ let tests =
(OTest(1).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual
(OTest(2).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual

testCase "GetHashCode with objects that overwrite it works" <| fun () ->
(Test(1).GetHashCode(), Test(1).GetHashCode()) ||> equal
(Test(2).GetHashCode(), Test(1).GetHashCode()) ||> notEqual

testCase "GetHashCode with same object works" <| fun () ->
let o = OTest(1)
let h1 = o.GetHashCode()
Expand Down

0 comments on commit b9b63a5

Please sign in to comment.