Skip to content

Commit 30aea47

Browse files
authored
Merge pull request #49 from CSBiology/deepHash
DeepHash
2 parents 86e7a0d + 90fd64c commit 30aea47

File tree

5 files changed

+328
-5
lines changed

5 files changed

+328
-5
lines changed

src/DynamicObj/DynamicObj.fs

+36-5
Original file line numberDiff line numberDiff line change
@@ -388,18 +388,49 @@ type DynamicObj() =
388388
static member (?<-) (lookup:#DynamicObj,name:string,value:'v) =
389389
lookup.SetProperty (name,value)
390390

391+
member this.ReferenceEquals (other: DynamicObj) = System.Object.ReferenceEquals(this,other)
392+
393+
member this.StructurallyEquals (other: DynamicObj) =
394+
this.GetHashCode() = other.GetHashCode()
395+
391396
override this.GetHashCode () =
392-
this.GetProperties(true)
393-
|> Seq.sortBy (fun pair -> pair.Key)
394-
|> HashCodes.boxHashKeyValSeq
395-
|> fun x -> x :?> int
397+
HashUtils.deepHash this
396398

397399
override this.Equals o =
398400
match o with
399401
| :? DynamicObj as other ->
400-
this.GetHashCode() = other.GetHashCode()
402+
this.StructurallyEquals(other)
401403
| _ -> false
402404

405+
and HashUtils =
406+
407+
static member deepHash (o:obj) =
408+
match o with
409+
| :? DynamicObj as o ->
410+
o.GetProperties(true)
411+
|> Seq.sortBy (fun pair -> pair.Key)
412+
|> HashCodes.boxHashKeyValSeqBy HashUtils.deepHash
413+
|> fun x -> x :?> int
414+
| :? string as s -> DynamicObj.HashCodes.hash s
415+
#if !FABLE_COMPILER
416+
| :? System.Collections.IDictionary as d ->
417+
let mutable en = d.GetEnumerator()
418+
[
419+
while en.MoveNext() do
420+
let c = en.Current :?> System.Collections.DictionaryEntry
421+
HashCodes.mergeHashes (hash c.Key) (HashUtils.deepHash c.Value)
422+
]
423+
|> List.reduce HashCodes.mergeHashes
424+
#endif
425+
| :? System.Collections.IEnumerable as e ->
426+
let en = e.GetEnumerator()
427+
[
428+
while en.MoveNext() do
429+
HashUtils.deepHash en.Current
430+
]
431+
|> List.reduce HashCodes.mergeHashes
432+
| _ -> DynamicObj.HashCodes.hash o
433+
403434
and CopyUtils =
404435

405436
/// <summary>

src/DynamicObj/HashCodes.fs

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
module DynamicObj.HashCodes
22

3+
// Taken from
4+
//https://softwareengineering.stackexchange.com/a/402543
5+
// Which points to a no-longer existing source in FSharp Core Compiler
6+
// But can be found in
7+
// https://github.com/dotnet/fsharp/blob/2edab1216843f20a00a7d8f171aca52cbc35d7fd/src/Compiler/Checking/AugmentWithHashCompare.fs#L171
8+
// Or Fables mirror
9+
// https://github.com/fable-compiler/Fable/blob/b0e640763fd90bd084f72531cb119d49a91ec077/src/fcs-fable/src/Compiler/Checking/AugmentWithHashCompare.fs#L171
310
let mergeHashes (hash1 : int) (hash2 : int) : int =
411
0x9e3779b9 + hash2 + (hash1 <<< 6) + (hash1 >>> 2)
512

@@ -46,4 +53,12 @@ let boxHashKeyValSeq (a: seq<System.Collections.Generic.KeyValuePair<'a,'b>>) :
4653
|> Seq.fold (fun acc o ->
4754
mergeHashes (hash o.Key) (hash o.Value)
4855
|> mergeHashes acc) 0
56+
|> box
57+
58+
let boxHashKeyValSeqBy (f : 'b -> int) (a: seq<System.Collections.Generic.KeyValuePair<'a,'b>>) : obj =
59+
a
60+
// from https://stackoverflow.com/a/53507559
61+
|> Seq.fold (fun acc o ->
62+
mergeHashes (hash o.Key) (f o.Value)
63+
|> mergeHashes acc) 0
4964
|> box

tests/DynamicObject.Tests/DynamicObject.Tests.fsproj

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<Compile Include="DynamicObj\DeepCopyProperties.fs" />
3434
<Compile Include="DynamicObj\Main.fs" />
3535
<Compile Include="ReflectionUtils.fs" />
36+
<Compile Include="HashUtils.fs" />
3637
<Compile Include="Inheritance.fs" />
3738
<Compile Include="Interface.fs" />
3839
<Compile Include="DynObj.fs" />
+275
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
module HashUtils.Tests
2+
3+
open System
4+
open Fable.Pyxpecto
5+
open DynamicObj
6+
open Fable.Core
7+
8+
let int1 = box 1
9+
let int2 = box 2
10+
let int3 = box 3
11+
let int4 = box 4
12+
13+
let intDict1 =
14+
let d = System.Collections.Generic.Dictionary<obj, obj>()
15+
d.Add(int1, int2)
16+
d.Add(int3, int4)
17+
d
18+
19+
let intDict1' =
20+
let d = System.Collections.Generic.Dictionary<obj, obj>()
21+
d.Add(int1, int2)
22+
d.Add(int3, int4)
23+
d
24+
25+
let intDict2 =
26+
let d = System.Collections.Generic.Dictionary<obj, obj>()
27+
d.Add(int1, int4)
28+
d.Add(int3, int2)
29+
d
30+
31+
let intDict3 =
32+
let d = System.Collections.Generic.Dictionary<obj, obj>()
33+
d.Add(int2, int1)
34+
d.Add(int4, int3)
35+
d
36+
37+
let intDict4 =
38+
let d = System.Collections.Generic.Dictionary<obj, obj>()
39+
d.Add(int1, int3)
40+
d.Add(int2, int4)
41+
d
42+
43+
let intList1 = [int1;int2;int3;int4]
44+
let intList1' = [int1;int2;int3;int4]
45+
let intList2 = [int1;int4;int3;int2]
46+
47+
let nestedList1 = [intList1;intList2]
48+
let nestedList1' = [intList1';intList2]
49+
let nestedList2 = [intList2;intList1]
50+
51+
let intArray1 = [|int1;int2;int3;int4|]
52+
let intArray1' = [|int1;int2;int3;int4|]
53+
let intArray2 = [|int1;int4;int3;int2|]
54+
55+
let intSeq1 = seq { yield int1; yield int2; yield int3; yield int4 }
56+
let intSeq1' = seq { yield int1; yield int2; yield int3; yield int4 }
57+
let intSeq2 = seq { yield int1; yield int4; yield int3; yield int2 }
58+
59+
let resizeArray1 = ResizeArray [int1;int2;int3;int4]
60+
let resizeArray1' = ResizeArray [int1;int2;int3;int4]
61+
let resizeArray2 = ResizeArray [int1;int4;int3;int2]
62+
63+
64+
let dynamicObjectWithInt1 =
65+
66+
let d = DynamicObj()
67+
d.SetProperty("a", int1)
68+
d.SetProperty("b", int2)
69+
d
70+
71+
let dynamicObjectWithInt1DiffKey =
72+
let d = DynamicObj()
73+
d.SetProperty("a", int1)
74+
d.SetProperty("c", int2)
75+
d
76+
77+
let dynamicObjectWithInt1' =
78+
let d = DynamicObj()
79+
d.SetProperty("a", int1)
80+
d.SetProperty("b", int2)
81+
d
82+
83+
let dynamicObjectWithInt2 =
84+
let d = DynamicObj()
85+
d.SetProperty("a", int2)
86+
d.SetProperty("b", int1)
87+
d
88+
89+
let dynamicObjectWithDict1 =
90+
let d = DynamicObj()
91+
d.SetProperty("a", intDict1)
92+
d.SetProperty("b", intDict2)
93+
d
94+
95+
let dynamicObjectWithDict1' =
96+
let d = DynamicObj()
97+
d.SetProperty("a", intDict1)
98+
d.SetProperty("b", intDict2)
99+
d
100+
101+
let dynamicObjectWithDict2 =
102+
let d = DynamicObj()
103+
d.SetProperty("a", intDict2)
104+
d.SetProperty("b", intDict1)
105+
d
106+
107+
let dynamicObjectWithDynamicObject1 =
108+
let d = DynamicObj()
109+
d.SetProperty("a", dynamicObjectWithInt1)
110+
d.SetProperty("b", dynamicObjectWithInt2)
111+
d
112+
113+
let dynamicObjectWithDynamicObject1' =
114+
let d = DynamicObj()
115+
d.SetProperty("a", dynamicObjectWithInt1)
116+
d.SetProperty("b", dynamicObjectWithInt2)
117+
d
118+
119+
let dynamicObjectWithDynamicObject2 =
120+
let d = DynamicObj()
121+
d.SetProperty("a", dynamicObjectWithInt2)
122+
d.SetProperty("b", dynamicObjectWithDict1)
123+
d
124+
125+
126+
let tests_Dictionary =
127+
testList "Dictionary" [
128+
testList "Shuffled Int" [
129+
testCase "1v1" <| fun _ ->
130+
Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1) "Same Dictionary should return consistent Hash"
131+
testCase "1v1'" <| fun _ ->
132+
Expect.equal (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict1') "Structurally equal Dictionary should return consistent Hash"
133+
testCase "1v2" <| fun _ ->
134+
Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict2) "Different Dictionary should return different Hash (1vs2)"
135+
testCase "1v3" <| fun _ ->
136+
Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (1vs3)"
137+
testCase "1v4" <| fun _ ->
138+
Expect.notEqual (HashUtils.deepHash intDict1) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (1vs4)"
139+
testCase "2v3" <| fun _ ->
140+
Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict3) "Different Dictionary should return different Hash (2vs3)"
141+
testCase "2v4" <| fun _ ->
142+
Expect.notEqual (HashUtils.deepHash intDict2) (HashUtils.deepHash intDict4) "Different Dictionary should return different Hash (2vs4)"
143+
144+
]
145+
]
146+
147+
let tests_Lists =
148+
testList "Lists" [
149+
testList "Shuffled Int" [
150+
testCase "1v1" <| fun _ ->
151+
Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1) "Same List should return consistent Hash"
152+
testCase "1v1'" <| fun _ ->
153+
Expect.equal (HashUtils.deepHash intList1) (HashUtils.deepHash intList1') "Structurally equal List should return consistent Hash"
154+
testCase "1v2" <| fun _ ->
155+
Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intList2) "Different List should return different Hash"
156+
]
157+
testList "Shuffled Nested" [
158+
testCase "1v1" <| fun _ ->
159+
Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1) "Same Nested List should return consistent Hash"
160+
testCase "1v1'" <| fun _ ->
161+
Expect.equal (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList1') "Structurally equal Nested List should return consistent Hash"
162+
testCase "1v2" <| fun _ ->
163+
Expect.notEqual (HashUtils.deepHash nestedList1) (HashUtils.deepHash nestedList2) "Different Nested List should return different Hash"
164+
165+
]
166+
]
167+
168+
let tests_Array =
169+
testList "Array" [
170+
testList "Shuffled Int" [
171+
testCase "1v1" <| fun _ ->
172+
Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1) "Same Array should return consistent Hash"
173+
testCase "1v1'" <| fun _ ->
174+
Expect.equal (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray1') "Structurally equal Array should return consistent Hash"
175+
testCase "1v2" <| fun _ ->
176+
Expect.notEqual (HashUtils.deepHash intArray1) (HashUtils.deepHash intArray2) "Different Array should return different Hash"
177+
]
178+
]
179+
180+
let tests_Seq =
181+
testList "Seq" [
182+
testList "Shuffled Int" [
183+
testCase "1v1" <| fun _ ->
184+
Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1) "Same Seq should return consistent Hash"
185+
testCase "1v1'" <| fun _ ->
186+
Expect.equal (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq1') "Structurally equal Seq should return consistent Hash"
187+
testCase "1v2" <| fun _ ->
188+
Expect.notEqual (HashUtils.deepHash intSeq1) (HashUtils.deepHash intSeq2) "Different Seq should return different Hash"
189+
]
190+
]
191+
192+
let tests_ResizeArray =
193+
testList "ResizeArray" [
194+
testList "Shuffled Int" [
195+
testCase "1v1" <| fun _ ->
196+
197+
Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1) "Same ResizeArray should return consistent Hash"
198+
testCase "1v1'" <| fun _ ->
199+
Expect.equal (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray1') "Structurally equal ResizeArray should return consistent Hash"
200+
testCase "1v2" <| fun _ ->
201+
Expect.notEqual (HashUtils.deepHash resizeArray1) (HashUtils.deepHash resizeArray2) "Different ResizeArray should return different Hash"
202+
]
203+
]
204+
205+
206+
let tests_DynamicObject =
207+
testList "DynamicObj" [
208+
testList "Shuffled Int" [
209+
testCase "1v1" <| fun _ ->
210+
Expect.equal (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1) "Same DynamicObject should return consistent Hash"
211+
testCase "1v1'" <| fun _ ->
212+
Expect.equal (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1') "Structurally equal DynamicObject should return consistent Hash"
213+
testCase "1v1DiffKey" <| fun _ ->
214+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt1DiffKey) "Different DynamicObject should return different Hash"
215+
testCase "1v2" <| fun _ ->
216+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithInt2) "Different DynamicObject should return different Hash"
217+
]
218+
testList "Shuffled Dict" [
219+
testCase "1v1" <| fun _ ->
220+
Expect.equal (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict1) "Same DynamicObject should return consistent Hash"
221+
testCase "1v1'" <| fun _ ->
222+
Expect.equal (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict1') "Structurally equal DynamicObject should return consistent Hash"
223+
testCase "1v2" <| fun _ ->
224+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDict2) "Different DynamicObject should return different Hash"
225+
]
226+
testList "Shuffled DynamicObject" [
227+
testCase "1v1" <| fun _ ->
228+
Expect.equal (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject1) "Same DynamicObject should return consistent Hash"
229+
testCase "1v1'" <| fun _ ->
230+
Expect.equal (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject1') "Structurally equal DynamicObject should return consistent Hash"
231+
testCase "1v2" <| fun _ ->
232+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithDynamicObject1) (HashUtils.deepHash dynamicObjectWithDynamicObject2) "Different DynamicObject should return different Hash"
233+
]
234+
testList "Shuffled Int AsOption" [
235+
testCase "1v1" <| fun _ ->
236+
Expect.equal (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1)) "Same DynamicObject should return consistent Hash"
237+
testCase "1v1'" <| fun _ ->
238+
Expect.equal (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1')) "Structurally equal DynamicObject should return consistent Hash"
239+
testCase "1v1DiffKey" <| fun _ ->
240+
Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt1DiffKey)) "Different DynamicObject should return different Hash"
241+
testCase "1v2" <| fun _ ->
242+
Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash (Some dynamicObjectWithInt2)) "Different DynamicObject should return different Hash"
243+
testCase "1 v None" <| fun _ ->
244+
Expect.notEqual (HashUtils.deepHash (Some dynamicObjectWithInt1)) (HashUtils.deepHash None) "Different DynamicObject should return different Hash"
245+
246+
]
247+
testList "Mixed" [
248+
testCase "Int vs Dict" <| fun _ ->
249+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithInt1) (HashUtils.deepHash dynamicObjectWithDict1) "Int vs Dict with same values should return different Hash"
250+
testCase "Dict vs DynObj" <| fun _ ->
251+
Expect.notEqual (HashUtils.deepHash dynamicObjectWithDict1) (HashUtils.deepHash dynamicObjectWithDynamicObject1) "Dict vs DynObj with same values should return different Hash"
252+
]
253+
]
254+
255+
256+
let tests_Mixed =
257+
testList "Mixed" [
258+
testCase "Int vs Dict" <| fun _ ->
259+
Expect.notEqual (HashUtils.deepHash int1) (HashUtils.deepHash intDict1) "Int vs Dict with same values should return different Hash"
260+
testCase "List vs Dict" <| fun _ ->
261+
Expect.notEqual (HashUtils.deepHash intList1) (HashUtils.deepHash intDict1) "List vs Dict with same values should return different Hash"
262+
]
263+
264+
265+
266+
267+
let main = testList "DeepHash" [
268+
tests_Dictionary
269+
tests_Lists
270+
tests_Array
271+
tests_Seq
272+
tests_ResizeArray
273+
tests_DynamicObject
274+
tests_Mixed
275+
]

tests/DynamicObject.Tests/Main.fs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ open Fable.Pyxpecto
44

55
let all = testSequenced <| testList "DynamicObj" [
66
ReflectionUtils.Tests.main
7+
HashUtils.Tests.main
78
CopyUtils.Tests.main
89
DynamicObj.Tests.main
910
DynObj.Tests.main

0 commit comments

Comments
 (0)