elvish/pkg/eval/vals/hash.go

78 lines
2.0 KiB
Go
Raw Normal View History

2018-02-15 17:14:05 +08:00
package vals
import (
"math"
"math/big"
2021-05-04 05:17:46 +08:00
"src.elv.sh/pkg/persistent/hash"
"src.elv.sh/pkg/persistent/hashmap"
)
// Hasher wraps the Hash method.
type Hasher interface {
// Hash computes the hash code of the receiver.
Hash() uint32
}
// Hash returns the 32-bit hash of a value. It is implemented for the builtin
// types bool and string, the File, List, Map types, StructMap types, and types
// satisfying the Hasher interface. For other values, it returns 0 (which is OK
// in terms of correctness).
func Hash(v any) uint32 {
switch v := v.(type) {
case bool:
if v {
return 1
}
return 0
case int:
return hash.UIntPtr(uintptr(v))
case *big.Int:
h := hash.DJBCombine(hash.DJBInit, uint32(v.Sign()))
for _, word := range v.Bits() {
h = hash.DJBCombine(h, hash.UIntPtr(uintptr(word)))
}
return h
case *big.Rat:
return hash.DJB(Hash(v.Num()), Hash(v.Denom()))
case float64:
return hash.UInt64(math.Float64bits(v))
case string:
return hash.String(v)
case Hasher:
return v.Hash()
case File:
return hash.UIntPtr(v.Fd())
case List:
h := hash.DJBInit
for it := v.Iterator(); it.HasElem(); it.Next() {
h = hash.DJBCombine(h, Hash(it.Elem()))
}
return h
case Map:
return hashMap(v.Iterator())
case StructMap:
return hashMap(iterateStructMap(v))
}
return 0
}
func hashMap(it hashmap.Iterator) uint32 {
// The iteration order of maps only depends on the hash of the keys. It is
// almost deterministic, with only one exception: when two keys have the
// same hash, they get produced in insertion order. As a result, it is
// possible for two maps that should be considered equal to produce entries
// in different orders.
//
// So instead of using hash.DJBCombine, combine the hash result from each
// key-value pair by summing, so that the order doesn't matter.
//
// TODO: This may not have very good hashing properties.
var h uint32
for ; it.HasElem(); it.Next() {
k, v := it.Elem()
h += hash.DJB(Hash(k), Hash(v))
}
return h
}