refactor: constructors, update docs
- NewCustomTable -> NewCustom - NewTableBy -> NewBy - NewTable -> New
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
package cuckoo
|
package cuckoo
|
||||||
|
|
||||||
// An EqualFunc determines whethers two keys are 'equal'. Keys that are 'equal'
|
// An EqualFunc determines whethers two keys are 'equal'. Keys that are 'equal'
|
||||||
// are teated as the same by the [Table]. A good EqualFunc is pure,
|
// are teated as the same by the [HashTable]. A good EqualFunc is pure,
|
||||||
// deterministic, and fast. By default, [NewTable] uses [DefaultEqualFunc].
|
// deterministic, and fast. By default, [New] uses [DefaultEqualFunc].
|
||||||
//
|
//
|
||||||
// This function MUST NOT return true if the [Hash] digest of two keys
|
// This function MUST NOT return true if the [Hash] digest of two keys
|
||||||
// are different: the [Table] will not work.
|
// are different: the [HashTable] will not work.
|
||||||
type EqualFunc[K any] = func(a, b K) bool
|
type EqualFunc[K any] = func(a, b K) bool
|
||||||
|
|
||||||
// DefaultEqualFunc compares two keys by strict equality. Returns true if the
|
// DefaultEqualFunc compares two keys by strict equality. Returns true if the
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func ExampleEqualFunc_badEqualFunc() {
|
|||||||
// Two users with the same ID are equal.
|
// Two users with the same ID are equal.
|
||||||
isEqual := func(a, b User) bool { return a.ID == b.ID }
|
isEqual := func(a, b User) bool { return a.ID == b.ID }
|
||||||
|
|
||||||
userbase := cuckoo.NewCustomTable[User, bool](makeHash(1), makeHash(2), isEqual)
|
userbase := cuckoo.NewCustom[User, bool](makeHash(1), makeHash(2), isEqual)
|
||||||
|
|
||||||
(userbase.Put(User{"1", "Robert Doe"}, true))
|
(userbase.Put(User{"1", "Robert Doe"}, true))
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func FuzzInsertLookup(f *testing.F) {
|
|||||||
fmt.Fprintf(os.Stderr, "seedA=%d seedB=%d capacity=%d growthFactor=%d\n",
|
fmt.Fprintf(os.Stderr, "seedA=%d seedB=%d capacity=%d growthFactor=%d\n",
|
||||||
seedA, seedB, capacity, growthFactor)
|
seedA, seedB, capacity, growthFactor)
|
||||||
|
|
||||||
actual := cuckoo.NewCustomTable[uint32, uint32](
|
actual := cuckoo.NewCustom[uint32, uint32](
|
||||||
offsetHash(seedA),
|
offsetHash(seedA),
|
||||||
offsetHash(seedB),
|
offsetHash(seedB),
|
||||||
func(a, b uint32) bool { return a == b },
|
func(a, b uint32) bool { return a == b },
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ func TestMaxEvictions(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
for i := 16; i < 116; i++ {
|
for i := 16; i < 116; i++ {
|
||||||
table := NewTable[int, bool](Capacity(i / 2))
|
table := New[int, bool](Capacity(i / 2))
|
||||||
expectedEvictions := 3 * math.Floor(math.Log2(float64(i)))
|
expectedEvictions := 3 * math.Floor(math.Log2(float64(i)))
|
||||||
|
|
||||||
assert.Equal(table.maxEvictions(), int(expectedEvictions))
|
assert.Equal(table.maxEvictions(), int(expectedEvictions))
|
||||||
@@ -20,7 +20,7 @@ func TestMaxEvictions(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
func TestLoad(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := NewTable[int, bool](Capacity(8))
|
table := New[int, bool](Capacity(8))
|
||||||
|
|
||||||
for i := range 16 {
|
for i := range 16 {
|
||||||
err := table.Put(i, true)
|
err := table.Put(i, true)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
func TestNewTable(t *testing.T) {
|
func TestNewTable(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
table := cuckoo.NewTable[int, bool]()
|
table := cuckoo.New[int, bool]()
|
||||||
|
|
||||||
assert.NotNil(table)
|
assert.NotNil(table)
|
||||||
assert.Zero(table.Size())
|
assert.Zero(table.Size())
|
||||||
@@ -23,7 +23,7 @@ func TestNewTable(t *testing.T) {
|
|||||||
func TestAddItem(t *testing.T) {
|
func TestAddItem(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key, value := 0, true
|
key, value := 0, true
|
||||||
table := cuckoo.NewTable[int, bool]()
|
table := cuckoo.New[int, bool]()
|
||||||
|
|
||||||
err := table.Put(key, value)
|
err := table.Put(key, value)
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ func TestAddItem(t *testing.T) {
|
|||||||
func TestPutOverwrite(t *testing.T) {
|
func TestPutOverwrite(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key, value, newValue := 0, 1, 2
|
key, value, newValue := 0, 1, 2
|
||||||
table := cuckoo.NewTable[int, int]()
|
table := cuckoo.New[int, int]()
|
||||||
(table.Put(key, value))
|
(table.Put(key, value))
|
||||||
|
|
||||||
err := table.Put(key, newValue)
|
err := table.Put(key, newValue)
|
||||||
@@ -50,7 +50,7 @@ func TestPutOverwrite(t *testing.T) {
|
|||||||
func TestSameHash(t *testing.T) {
|
func TestSameHash(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
hash := func(int) uint64 { return 0 }
|
hash := func(int) uint64 { return 0 }
|
||||||
table := cuckoo.NewCustomTable[int, bool](hash, hash, cuckoo.DefaultEqualFunc[int])
|
table := cuckoo.NewCustom[int, bool](hash, hash, cuckoo.DefaultEqualFunc[int])
|
||||||
|
|
||||||
errA := table.Put(0, true)
|
errA := table.Put(0, true)
|
||||||
errB := table.Put(1, true)
|
errB := table.Put(1, true)
|
||||||
@@ -63,14 +63,14 @@ func TestSameHash(t *testing.T) {
|
|||||||
|
|
||||||
func TestStartingCapacity(t *testing.T) {
|
func TestStartingCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewTable[int, bool](cuckoo.Capacity(64))
|
table := cuckoo.New[int, bool](cuckoo.Capacity(64))
|
||||||
|
|
||||||
assert.Equal(uint64(128), table.TotalCapacity())
|
assert.Equal(uint64(128), table.TotalCapacity())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResizeCapacity(t *testing.T) {
|
func TestResizeCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewTable[int, bool](
|
table := cuckoo.New[int, bool](
|
||||||
cuckoo.Capacity(8),
|
cuckoo.Capacity(8),
|
||||||
cuckoo.GrowthFactor(2),
|
cuckoo.GrowthFactor(2),
|
||||||
)
|
)
|
||||||
@@ -85,7 +85,7 @@ func TestResizeCapacity(t *testing.T) {
|
|||||||
|
|
||||||
func TestPutMany(t *testing.T) {
|
func TestPutMany(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
expected, actual := map[int]bool{}, cuckoo.NewTable[int, bool]()
|
expected, actual := map[int]bool{}, cuckoo.New[int, bool]()
|
||||||
|
|
||||||
for i := range 1_000 {
|
for i := range 1_000 {
|
||||||
expected[i] = true
|
expected[i] = true
|
||||||
@@ -100,7 +100,7 @@ func TestPutMany(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetMany(t *testing.T) {
|
func TestGetMany(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewTable[int, bool]()
|
table := cuckoo.New[int, bool]()
|
||||||
|
|
||||||
for i := range 1_000 {
|
for i := range 1_000 {
|
||||||
err := table.Put(i, true)
|
err := table.Put(i, true)
|
||||||
@@ -121,7 +121,7 @@ func TestGetMany(t *testing.T) {
|
|||||||
func TestDropExistingItem(t *testing.T) {
|
func TestDropExistingItem(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key, value := 0, true
|
key, value := 0, true
|
||||||
table := cuckoo.NewTable[int, bool]()
|
table := cuckoo.New[int, bool]()
|
||||||
(table.Put(key, value))
|
(table.Put(key, value))
|
||||||
|
|
||||||
err := table.Drop(key)
|
err := table.Drop(key)
|
||||||
@@ -134,7 +134,7 @@ func TestDropExistingItem(t *testing.T) {
|
|||||||
func TestDropNoItem(t *testing.T) {
|
func TestDropNoItem(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key := 0
|
key := 0
|
||||||
table := cuckoo.NewTable[int, bool]()
|
table := cuckoo.New[int, bool]()
|
||||||
|
|
||||||
err := table.Drop(key)
|
err := table.Drop(key)
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ func TestDropNoItem(t *testing.T) {
|
|||||||
func TestDropItemCapacity(t *testing.T) {
|
func TestDropItemCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key := 0
|
key := 0
|
||||||
table := cuckoo.NewTable[int, bool](
|
table := cuckoo.New[int, bool](
|
||||||
cuckoo.Capacity(64),
|
cuckoo.Capacity(64),
|
||||||
cuckoo.GrowthFactor(2),
|
cuckoo.GrowthFactor(2),
|
||||||
)
|
)
|
||||||
@@ -164,7 +164,7 @@ func TestDropItemCapacity(t *testing.T) {
|
|||||||
func TestPutNoCapacity(t *testing.T) {
|
func TestPutNoCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
key, value := 0, true
|
key, value := 0, true
|
||||||
table := cuckoo.NewTable[int, bool](
|
table := cuckoo.New[int, bool](
|
||||||
cuckoo.Capacity(0),
|
cuckoo.Capacity(0),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ func TestPutNoCapacity(t *testing.T) {
|
|||||||
|
|
||||||
func TestBadHashCapacity(t *testing.T) {
|
func TestBadHashCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewCustomTable[int, bool](
|
table := cuckoo.NewCustom[int, bool](
|
||||||
func(int) uint64 { return 0 },
|
func(int) uint64 { return 0 },
|
||||||
func(int) uint64 { return 0 },
|
func(int) uint64 { return 0 },
|
||||||
func(a, b int) bool { return a == b },
|
func(a, b int) bool { return a == b },
|
||||||
@@ -197,7 +197,7 @@ func TestBadHashCapacity(t *testing.T) {
|
|||||||
|
|
||||||
func TestDropResizeCapacity(t *testing.T) {
|
func TestDropResizeCapacity(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewTable[int, bool](
|
table := cuckoo.New[int, bool](
|
||||||
cuckoo.Capacity(10),
|
cuckoo.Capacity(10),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -217,9 +217,7 @@ func TestNewTableBy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
table := cuckoo.NewTableBy[User, bool](
|
table := cuckoo.NewBy[User, bool](func(u User) string { return u.id })
|
||||||
func(u User) string { return u.id },
|
|
||||||
)
|
|
||||||
|
|
||||||
err := table.Put(User{nil, "1", "Robert"}, true)
|
err := table.Put(User{nil, "1", "Robert"}, true)
|
||||||
|
|
||||||
|
|||||||
4
doc.go
4
doc.go
@@ -1,8 +1,8 @@
|
|||||||
// Package cuckoo provides a hash table that uses cuckoo hashing to achieve
|
// Package cuckoo provides a hash table that uses cuckoo hashing to achieve
|
||||||
// a worst-case O(1) lookup time.
|
// a worst-case O(1) lookup time.
|
||||||
//
|
//
|
||||||
// While a [NewTable] only supports comparable keys by default, you can create
|
// While a [New] only supports comparable keys by default, you can create
|
||||||
// a table with any key type using [NewCustomTable]. Custom [Hash] functions and
|
// a table with any key type using [NewCustom]. Custom [Hash] functions and
|
||||||
// key comparison are also supported.
|
// key comparison are also supported.
|
||||||
//
|
//
|
||||||
// See more: https://en.wikipedia.org/wiki/Cuckoo_hashing
|
// See more: https://en.wikipedia.org/wiki/Cuckoo_hashing
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Example_basic() {
|
func Example_basic() {
|
||||||
table := cuckoo.NewTable[int, string]()
|
table := cuckoo.New[int, string]()
|
||||||
|
|
||||||
if err := table.Put(1, "Hello, World!"); err != nil {
|
if err := table.Put(1, "Hello, World!"); err != nil {
|
||||||
fmt.Println("Put error:", err)
|
fmt.Println("Put error:", err)
|
||||||
|
|||||||
4
hash.go
4
hash.go
@@ -7,9 +7,9 @@ import (
|
|||||||
// A Hash function maps any data to a fixed-length value (in this case, a
|
// A Hash function maps any data to a fixed-length value (in this case, a
|
||||||
// [uint64]).
|
// [uint64]).
|
||||||
//
|
//
|
||||||
// It is used by the [Table] to evenly distribute values
|
// It is used by the [HashTable] to evenly distribute values
|
||||||
// amongst its slots. A good hash function is uniform, [chaotic], and
|
// amongst its slots. A good hash function is uniform, [chaotic], and
|
||||||
// deterministic. [Table] uses [NewDefaultHash] by default, which is built on
|
// deterministic. [HashTable] uses [NewDefaultHash] by default, which is built on
|
||||||
// [maphash.Comparable].
|
// [maphash.Comparable].
|
||||||
//
|
//
|
||||||
// [chaotic]: https://en.wikipedia.org/wiki/Avalanche_effect
|
// [chaotic]: https://en.wikipedia.org/wiki/Avalanche_effect
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A HashTable is hash table that uses cuckoo hashing to resolve collision. Create
|
// A HashTable which uses cuckoo hashing to resolve collision. Create
|
||||||
// one with [NewTable]. Or if you want more granularity, use [NewTableBy] or
|
// one with [New]. Or if you want more granularity, use [NewBy] or
|
||||||
// [NewCustomTable].
|
// [NewCustom].
|
||||||
type HashTable[K, V any] struct {
|
type HashTable[K, V any] struct {
|
||||||
tableA, tableB table[K, V]
|
tableA, tableB table[K, V]
|
||||||
growthFactor uint64
|
growthFactor uint64
|
||||||
@@ -192,9 +192,9 @@ func (t *HashTable[K, V]) String() string {
|
|||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCustomTable creates a [HashTable] with custom [Hash] and [EqualFunc]
|
// NewCustom creates a [HashTable] with custom [Hash] and [EqualFunc]
|
||||||
// functions, along with any [Option] the user provides.
|
// functions, along with any [Option] the user provides.
|
||||||
func NewCustomTable[K, V any](hashA, hashB Hash[K], compare EqualFunc[K], options ...Option) *HashTable[K, V] {
|
func NewCustom[K, V any](hashA, hashB Hash[K], compare EqualFunc[K], options ...Option) *HashTable[K, V] {
|
||||||
settings := &settings{
|
settings := &settings{
|
||||||
growthFactor: DefaultGrowthFactor,
|
growthFactor: DefaultGrowthFactor,
|
||||||
bucketSize: DefaultCapacity,
|
bucketSize: DefaultCapacity,
|
||||||
@@ -217,10 +217,10 @@ func pipe[X, Y, Z any](a func(X) Y, b func(Y) Z) func(X) Z {
|
|||||||
return func(x X) Z { return b(a(x)) }
|
return func(x X) Z { return b(a(x)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTableBy creates a [HashTable] for any key type by using keyFunc to derive a
|
// NewBy creates a [HashTable] for any key type by using keyFunc to derive a
|
||||||
// comparable key. Two keys with the same derived key are treated as equal.
|
// comparable key. Two keys with the same derived key are treated as equal.
|
||||||
func NewTableBy[K, V any, C comparable](keyFunc func(K) C, options ...Option) *HashTable[K, V] {
|
func NewBy[K, V any, C comparable](keyFunc func(K) C, options ...Option) *HashTable[K, V] {
|
||||||
return NewCustomTable[K, V](
|
return NewCustom[K, V](
|
||||||
pipe(keyFunc, NewDefaultHash[C]()),
|
pipe(keyFunc, NewDefaultHash[C]()),
|
||||||
pipe(keyFunc, NewDefaultHash[C]()),
|
pipe(keyFunc, NewDefaultHash[C]()),
|
||||||
func(a, b K) bool { return keyFunc(a) == keyFunc(b) },
|
func(a, b K) bool { return keyFunc(a) == keyFunc(b) },
|
||||||
@@ -228,10 +228,10 @@ func NewTableBy[K, V any, C comparable](keyFunc func(K) C, options ...Option) *H
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTable creates a [HashTable] using the default [Hash] and [EqualFunc]. Use
|
// New creates a [HashTable] using the default [Hash] and [EqualFunc]. Use
|
||||||
// the [Option] functions to configure its behavior. Note that this constructor
|
// the [Option] functions to configure its behavior. Note that this constructor
|
||||||
// is only provided for comparable keys. For arbitrary keys, consider
|
// is only provided for comparable keys. For arbitrary keys, consider
|
||||||
// [NewTableBy] or [NewCustomTable].
|
// [NewBy] or [NewCustom].
|
||||||
func NewTable[K comparable, V any](options ...Option) *HashTable[K, V] {
|
func New[K comparable, V any](options ...Option) *HashTable[K, V] {
|
||||||
return NewCustomTable[K, V](NewDefaultHash[K](), NewDefaultHash[K](), DefaultEqualFunc[K], options...)
|
return NewCustom[K, V](NewDefaultHash[K](), NewDefaultHash[K](), DefaultEqualFunc[K], options...)
|
||||||
}
|
}
|
||||||
|
|||||||
18
settings.go
18
settings.go
@@ -2,18 +2,18 @@ package cuckoo
|
|||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// DefaultCapacity is the initial capacity of a [Table]. It is inspired from
|
// DefaultCapacity is the initial capacity of a [HashTable]. It is inspired from
|
||||||
// Java's [HashMap] implementation, which also uses 16.
|
// Java's [HashMap] implementation, which also uses 16.
|
||||||
//
|
//
|
||||||
// [HashMap]: https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#HashMap--
|
// [HashMap]: https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html#HashMap--
|
||||||
const DefaultCapacity uint64 = 16
|
const DefaultCapacity uint64 = 16
|
||||||
|
|
||||||
// DefaultGrowthFactor is the standard resize multiplier for a [Table]. Most
|
// DefaultGrowthFactor is the standard resize multiplier for a [HashTable]. Most
|
||||||
// hash table implementations use 2.
|
// implementations use 2.
|
||||||
const DefaultGrowthFactor uint64 = 2
|
const DefaultGrowthFactor uint64 = 2
|
||||||
|
|
||||||
// defaultMinimumLoad is the default lowest acceptable occupancy of a [Table].
|
// defaultMinimumLoad is the default lowest acceptable occupancy of a [HashTable].
|
||||||
// The higher the minimum load, the more likely that a [Table.Put] will not
|
// The higher the minimum load, the more likely that a [HashTable.Put] will not
|
||||||
// succeed. The value of 5% is taken from [libcuckoo].
|
// succeed. The value of 5% is taken from [libcuckoo].
|
||||||
//
|
//
|
||||||
// [libcuckoo]: https://github.com/efficient/libcuckoo/blob/656714705a055df2b7a605eb3c71586d9da1e119/libcuckoo/cuckoohash_config.hh#L21
|
// [libcuckoo]: https://github.com/efficient/libcuckoo/blob/656714705a055df2b7a605eb3c71586d9da1e119/libcuckoo/cuckoohash_config.hh#L21
|
||||||
@@ -25,11 +25,11 @@ type settings struct {
|
|||||||
bucketSize uint64
|
bucketSize uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Option modifies the settings of a [Table]. It is used in its constructors
|
// An Option modifies the settings of a [HashTable]. It is used in its constructors
|
||||||
// like [NewTable], for example.
|
// like [New], for example.
|
||||||
type Option func(*settings)
|
type Option func(*settings)
|
||||||
|
|
||||||
// Capacity modifies the starting capacity of each bucket of the [Table]. The
|
// Capacity modifies the starting capacity of each bucket of the [HashTable]. The
|
||||||
// value must be non-negative.
|
// value must be non-negative.
|
||||||
func Capacity(value int) Option {
|
func Capacity(value int) Option {
|
||||||
if value < 0 {
|
if value < 0 {
|
||||||
@@ -39,7 +39,7 @@ func Capacity(value int) Option {
|
|||||||
return func(s *settings) { s.bucketSize = uint64(value) }
|
return func(s *settings) { s.bucketSize = uint64(value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// GrowthFactor controls how much the capacity of the [Table] multiplies when
|
// GrowthFactor controls how much the capacity of the [HashTable] multiplies when
|
||||||
// it must resize. The value must be greater than 1.
|
// it must resize. The value must be greater than 1.
|
||||||
func GrowthFactor(value int) Option {
|
func GrowthFactor(value int) Option {
|
||||||
if value < 2 {
|
if value < 2 {
|
||||||
|
|||||||
Reference in New Issue
Block a user