Skip to content

Commit 0ad45d0

Browse files
committed
Persistent vector and list hash to the same value
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent 9b5bab8 commit 0ad45d0

5 files changed

Lines changed: 57 additions & 6 deletions

File tree

pkg/lang/apersistentvector.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type (
3434
)
3535

3636
var (
37+
_ Hasher = (*PersistentVector)(nil)
38+
3739
_ ASeq = (*apvSeq)(nil)
3840
_ IndexedSeq = (*apvSeq)(nil)
3941
_ IReduce = (*apvSeq)(nil)
@@ -203,6 +205,19 @@ func apersistentVectorHashEq(hc *uint32, a APersistentVector) uint32 {
203205
return hash
204206
}
205207

208+
func apersistentVectorHash(hc *uint32, a APersistentVector) uint32 {
209+
if *hc != 0 {
210+
return *hc
211+
}
212+
var n int
213+
var hash uint32 = 1
214+
for ; n < a.Count(); n++ {
215+
hash = 31*hash + Hash(a.Nth(n))
216+
}
217+
*hc = hash
218+
return hash
219+
}
220+
206221
func apersistentVectorInvoke(a APersistentVector, args ...any) any {
207222
if len(args) != 1 {
208223
panic(NewIllegalArgumentError(fmt.Sprintf("vector apply expects one argument, got %d", len(args))))

pkg/lang/aseq.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ func aseqHash(hc *uint32, a ASeq) uint32 {
9292
hash := uint32(1)
9393
for s := a.Seq(); s != nil; s = s.Next() {
9494
var h uint32
95-
if s.First() != nil {
96-
h = Hash(s.First())
95+
first := s.First()
96+
if first != nil {
97+
h = Hash(first)
9798
}
9899
hash = 31*hash + h
99100
}

pkg/lang/hashes_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package lang
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestHashEquivalences(t *testing.T) {
9+
// test cases are sets of values that should hash to the same value
10+
testCases := [][]any{
11+
{nil, uint32(0)},
12+
{NewList(NewKeyword("a"), NewKeyword("b")), NewVector(NewKeyword("a"), NewKeyword("b"))},
13+
}
14+
15+
for i, group := range testCases {
16+
group := group // capture range variable
17+
t.Run(fmt.Sprintf("group_%d", i), func(t *testing.T) {
18+
if len(group) < 2 {
19+
t.Fatalf("test case must have at least two elements")
20+
}
21+
expectedHash := Hash(group[0])
22+
for _, v := range group[1:] {
23+
h := Hash(v)
24+
if h != expectedHash {
25+
t.Errorf("Hash(%v [%T]) = %d; want %d", v, v, h, expectedHash)
26+
}
27+
}
28+
})
29+
}
30+
}

pkg/lang/list.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var (
4141
_ IReduce = (*EmptyList)(nil)
4242
_ IReduceInit = (*EmptyList)(nil)
4343
_ IHashEq = (*EmptyList)(nil)
44+
_ Hasher = (*EmptyList)(nil)
4445
)
4546

4647
func (e *EmptyList) xxx_sequential() {}
@@ -109,10 +110,6 @@ func (e *EmptyList) Meta() IPersistentMap {
109110
return e.meta
110111
}
111112

112-
func (e *EmptyList) Hash() uint32 {
113-
return 1
114-
}
115-
116113
var (
117114
emptyHashOrdered = murmur3.HashOrdered(nil, HashEq)
118115
)
@@ -121,6 +118,10 @@ func (e *EmptyList) HashEq() uint32 {
121118
return emptyHashOrdered
122119
}
123120

121+
func (e *EmptyList) Hash() uint32 {
122+
return 1
123+
}
124+
124125
func (e *EmptyList) WithMeta(meta IPersistentMap) any {
125126
if e.meta == meta {
126127
return e

pkg/lang/vector.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ func (v *Vector) HashEq() uint32 {
227227
return apersistentVectorHashEq(&v.hasheq, v)
228228
}
229229

230+
func (v *Vector) Hash() uint32 {
231+
return apersistentVectorHash(&v.hash, v)
232+
}
233+
230234
func (v *Vector) ReduceInit(f IFn, init any) any {
231235
res := init
232236
for i := 0; i < v.Count(); i++ {

0 commit comments

Comments
 (0)