diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..87f1df6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.go] +indent_style = tab +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/README.md b/README.md index 44e4a9c..9a9c58c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[中文](README_CN.md) +

DAO

logo @@ -10,375 +12,363 @@
-### 简介 +### Description + +`DAO` is a library of generic-based data structures and algorithms that complements the standard library in terms of +data containers and algorithms to simplify business development. -`DAO` 是一个基于泛型的数据结构与算法库 +### Index -### 目录 +- [Description](#description) +- [Index](#index) +- [Vector](#vector) + - [Unique](#unique) + - [Sort](#sort) + - [Filter](#filter) +- [Heap](#heap) + - [N-Way Heap](#n-way-heap) + - [N-Way Indexed Heap](#n-way-indexed-heap) +- [Stack](#stack) +- [Queue](#queue) +- [Deque](#deque) +- [LinkedList](#linkedlist) +- [RBTree](#rbtree) +- [Dict](#dict) +- [HashMap](#hashmap) +- [Segment Tree](#segment-tree) +- [Benchmark](#benchmark) -- [简介](#简介) -- [目录](#目录) -- [动态数组](#动态数组) - - [去重](#去重) - - [排序](#排序) - - [过滤](#过滤) -- [堆](#堆) - - [二叉堆](#二叉堆) - - [四叉堆](#四叉堆) - - [八叉堆](#八叉堆) -- [栈](#栈) -- [队列](#队列) -- [双端队列](#双端队列) -- [双向链表](#双向链表) -- [红黑树](#红黑树) -- [字典树](#字典树) -- [哈希表](#哈希表) -- [线段树](#线段树) -- [基准测试](#基准测试) -### 动态数组 +### Vector -#### 去重 +#### Unique ```go package main import ( - "fmt" - "github.com/lxzan/dao/vector" + "fmt" + "github.com/lxzan/dao/vector" ) func main() { - var v = vector.NewFromInts(1, 3, 5, 3) - v.Uniq() - fmt.Printf("%v", v.Elem()) + var v = vector.NewFromInts(1, 3, 5, 3) + v.Unique() + fmt.Printf("%v", v.Elem()) } ``` -#### 排序 +#### Sort ```go package main import ( - "fmt" - "github.com/lxzan/dao/vector" + "fmt" + "github.com/lxzan/dao/vector" ) func main() { - var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) - v.Sort() - fmt.Printf("%v", v.Elem()) + var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) + v.Sort() + fmt.Printf("%v", v.Elem()) } ``` -#### 过滤 +#### Filter ```go package main import ( - "fmt" - "github.com/lxzan/dao/vector" + "fmt" + "github.com/lxzan/dao/vector" ) func main() { - var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) - v.Filter(func(i int, v vector.Int) bool { - return v.GetID()%2 == 0 - }) - fmt.Printf("%v", v.Elem()) + var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) + v.Filter(func(i int, v vector.Int) bool { + return v.GetID()%2 == 0 + }) + fmt.Printf("%v", v.Elem()) } ``` -### 堆 +### Heap -**堆** 又称之为优先队列, 堆顶元素总是最大或最小的. 常用的是四叉堆, `Push/Pop` 性能较为均衡. +**Heap**, also known as a priority queue, where the top element of the heap is always the largest or smallest. Commonly +used is the quadruple heap, `Push/Pop` is more balanced. Using `y=pow(2,x)` as the number of forks, to speed up +parent-child computation. -#### 二叉堆 +#### N-Way Heap ```go package main import ( - "github.com/lxzan/dao/heap" - "github.com/lxzan/dao/types/cmp" + "github.com/lxzan/dao/heap" + "github.com/lxzan/dao/types/cmp" ) func main() { - var h = heap.NewWithForks(heap.Binary, cmp.Less[int]) - h.Push(1) - h.Push(3) - h.Push(5) - h.Push(2) - h.Push(4) - h.Push(6) - for h.Len() > 0 { - println(h.Pop()) - } + var h = heap.NewWithWays(heap.Binary, cmp.Less[int]) + h.Push(1) + h.Push(3) + h.Push(5) + h.Push(2) + h.Push(4) + h.Push(6) + for h.Len() > 0 { + println(h.Pop()) + } } ``` -#### 四叉堆 - -```go -package main - -import ( - "github.com/lxzan/dao/heap" - "github.com/lxzan/dao/types/cmp" -) - -func main() { - var h = heap.NewWithForks(heap.Quadratic, cmp.Less[int]) - h.Push(1) - h.Push(3) - h.Push(5) - h.Push(2) - h.Push(4) - h.Push(6) - for h.Len() > 0 { - println(h.Pop()) - } -} +#### N-Way Indexed Heap -``` - -#### 八叉堆 +Extends the normal heap with update and delete functions, often used in time heap algorithms. ```go package main import ( - "github.com/lxzan/dao/heap" - "github.com/lxzan/dao/types/cmp" + "github.com/lxzan/dao/heap" ) func main() { - var h = heap.NewWithForks(heap.Octal, cmp.Less[int]) - h.Push(1) - h.Push(3) - h.Push(5) - h.Push(2) - h.Push(4) - h.Push(6) - for h.Len() > 0 { - println(h.Pop()) - } + var h = heap.NewIndexedHeap[int, struct{}](heap.Quadratic, func(a, b int) bool { return a > b }) + h.Push(1, struct{}{}) + h.Push(3, struct{}{}) + h.Push(5, struct{}{}) + h.Push(2, struct{}{}) + h.Push(4, struct{}{}) + h.Push(6, struct{}{}) + h.DeleteByIndex(5) + h.UpdateKeyByIndex(3, 7) + for h.Len() > 0 { + println(h.Pop().Key()) + } } ``` -### 栈 +### Stack -**栈** 先进后出 (`LIFO`) 的数据结构 +**Stack** Last in first out (`LIFO`) data structure ```go package main import ( - "github.com/lxzan/dao/stack" + "github.com/lxzan/dao/stack" ) func main() { - var s stack.Stack[int] - s.Push(1) - s.Push(3) - s.Push(5) - for s.Len() > 0 { - println(s.Pop()) - } + var s stack.Stack[int] + s.Push(1) + s.Push(3) + s.Push(5) + for s.Len() > 0 { + println(s.Pop()) + } } ``` -### 队列 +### Queue -**队列** 先进先出 (`FIFO`) 的数据结构. `dao/queue` 在全部元素弹出后会自动重置, 复用内存空间 +**Queue** First in first out (`FIFO`) data structure. The `dao/queue` is automatically reset when all elements are +ejected, reusing memory space. ```go package main import ( - "github.com/lxzan/dao/queue" + "github.com/lxzan/dao/queue" ) func main() { - var s = queue.New[int](0) - s.Push(1) - s.Push(3) - s.Push(5) - for s.Len() > 0 { - println(s.Pop()) - } + var s = queue.New[int](0) + s.Push(1) + s.Push(3) + s.Push(5) + for s.Len() > 0 { + println(s.Pop()) + } } ``` -### 双端队列 +### Deque -**双端队列** 类似于双向链表, 两端均可高效执行插入删除操作. +**Deque** are similar to doubly linked list, where insertion and deletion operations can be +performed efficiently at both ends. -`dao/deque` 基于数组下标模拟指针实现, 删除后的空间后续仍可复用, 且不依赖 `sync.Pool` +`dao/deque` is based on array subscripts to emulate pointers, the deleted space can still be reused later, and does not +depend on `sync.Pool`. ```go package main import ( - "fmt" - "github.com/lxzan/dao/deque" + "fmt" + "github.com/lxzan/dao/deque" ) func main() { - var list = deque.New[int](8) - list.PushBack(1) - list.PushBack(3) - list.PushBack(5) - list.PushBack(7) - list.PushBack(9) - for i := list.Front(); i != nil; i = list.Get(i.Next()) { - fmt.Printf("%v ", i.Value()) - } - - println() - for i := list.Back(); i != nil; i = list.Get(i.Prev()) { - fmt.Printf("%v ", i.Value()) - } + var list = deque.New[int](8) + list.PushBack(1) + list.PushBack(3) + list.PushBack(5) + list.PushBack(7) + list.PushBack(9) + for i := list.Front(); i != nil; i = list.Get(i.Next()) { + fmt.Printf("%v ", i.Value()) + } + + println() + for i := list.Back(); i != nil; i = list.Get(i.Prev()) { + fmt.Printf("%v ", i.Value()) + } } ``` -### 双向链表 +### LinkedList ```go package main import ( - "fmt" - "github.com/lxzan/dao/linkedlist" + "fmt" + "github.com/lxzan/dao/linkedlist" ) func main() { - var list = linkedlist.New[int]() - list.PushBack(1) - list.PushBack(3) - list.PushBack(5) - list.PushBack(7) - list.PushBack(9) - for i := list.Front(); i != nil; i = i.Next() { - fmt.Printf("%v ", i.Value) - } - - println() - for i := list.Back(); i != nil; i = i.Prev() { - fmt.Printf("%v ", i.Value) - } + var list = linkedlist.New[int]() + list.PushBack(1) + list.PushBack(3) + list.PushBack(5) + list.PushBack(7) + list.PushBack(9) + for i := list.Front(); i != nil; i = i.Next() { + fmt.Printf("%v ", i.Value) + } + + println() + for i := list.Back(); i != nil; i = i.Prev() { + fmt.Printf("%v ", i.Value) + } } ``` -### 红黑树 +### RBTree -高性能红黑树实现, 可作为内存数据库使用. +A high-performance red-black tree implementation that can be used as an in-memory database. ```go package main import ( - "github.com/lxzan/dao/rbtree" + "github.com/lxzan/dao/rbtree" ) func main() { - var tree = rbtree.New[int, struct{}]() - for i := 0; i < 10; i++ { - tree.Set(i, struct{}{}) - } - - var results = tree. - NewQuery(). - Left(func(key int) bool { return key >= 3 }). - Right(func(key int) bool { return key <= 5 }). - Order(rbtree.ASC). - FindAll() - for _, item := range results { - println(item.Key) - } + var tree = rbtree.New[int, struct{}]() + for i := 0; i < 10; i++ { + tree.Set(i, struct{}{}) + } + + var results = tree. + NewQuery(). + Left(func(key int) bool { return key >= 3 }). + Right(func(key int) bool { return key <= 5 }). + Order(rbtree.ASC). + FindAll() + for _, item := range results { + println(item.Key) + } } ``` -### 字典树 +### Dict -**字典树** 又叫前缀树, 可以高效匹配字符串前缀. `dao/dict` 可以动态配置槽位宽度(由索引控制). +**Dict** aka prefix tree, efficiently matches string prefixes. `dao/dict` can dynamically configure the width +of slots (controlled by the index). -注意: 合理设置索引, 超出索引长度的字符不能被索引优化 +Note: Set the index reasonably, characters beyond the index length can not be optimized for indexing. ```go package main import ( - "github.com/lxzan/dao/dict" + "github.com/lxzan/dao/dict" ) func main() { - var tree = dict.New[int]() - tree.Set("listen", 1) - tree.Set("list", 2) - tree.Set("often", 3) - tree.Set("oh!", 4) - tree.Set("haha", 5) - tree.Set("", 6) - - tree.Match("list", func(key string, value int) bool { - println(key, value) - return true - }) + var tree = dict.New[int]() + tree.Set("listen", 1) + tree.Set("list", 2) + tree.Set("often", 3) + tree.Set("oh!", 4) + tree.Set("haha", 5) + tree.Set("", 6) + + tree.Match("list", func(key string, value int) bool { + println(key, value) + return true + }) } ``` -### 哈希表 +### HashMap + +An alias for `Runtime Map`, extending `Keys`, `Values`, `Range` and other utility methods. ```go package main import ( - "github.com/lxzan/dao/hashmap" + "github.com/lxzan/dao/hashmap" ) func main() { - var m = hashmap.New[string, int](8) - m.Set("a", 1) - m.Set("b", 2) - m.Set("c", 3) - m.Range(func(key string, val int) bool { - println(key, val) - return true - }) + var m = hashmap.New[string, int](8) + m.Set("a", 1) + m.Set("b", 2) + m.Set("c", 3) + m.Range(func(key string, val int) bool { + println(key, val) + return true + }) } ``` -### 线段树 +### Segment Tree -**线段树** 是一种二叉树,它的每个节点都表示一个区间。 线段树的特点是可以在`O(logn)`的时间内进行区间查询和区间更新。 +**Segment Tree** is a binary tree in which each node represents an interval. Line segment trees are characterized by the +ability to perform interval queries and interval updates in `O(logn)` time. ```go package main import ( - tree "github.com/lxzan/dao/segment_tree" + tree "github.com/lxzan/dao/segment_tree" ) func main() { - var data = []tree.Int64{1, 3, 5, 7, 9, 2, 4, 6, 8, 10} - var lines = tree.New[tree.Int64Schema, tree.Int64](data) - var result = lines.Query(0, 10) - println(result.MinValue, result.MaxValue, result.Sum) + var data = []tree.Int64{1, 3, 5, 7, 9, 2, 4, 6, 8, 10} + var lines = tree.New[tree.Int64Schema, tree.Int64](data) + var result = lines.Query(0, 10) + println(result.MinValue, result.MaxValue, result.Sum) } ``` -### 基准测试 +### Benchmark - 1,000 elements diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..763c0c4 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,431 @@ +
+

DAO

+ logo +
道生一, 一生二, 二生三, 三生万物; 万物负阴而抱阳, 冲气以为和
+
+ +
+ +[![Build Status](https://github.com/lxzan/dao/workflows/Go%20Test/badge.svg?branch=main)](https://github.com/lxzan/dao/actions?query=branch%3Amain) [![codecov](https://codecov.io/gh/lxzan/dao/graph/badge.svg?token=BQM1JHCDEE)](https://codecov.io/gh/lxzan/dao) [![go-version](https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square&logo=go)](https://github.com/lxzan/dao) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +
+ +### 简介 + +`DAO` 是一个基于泛型的数据结构与算法库, 补充标准库在数据容器和算法方面的不足, 简化业务开发. + +### 目录 + +- [简介](#简介) +- [目录](#目录) +- [动态数组](#动态数组) + - [去重](#去重) + - [排序](#排序) + - [过滤](#过滤) +- [堆](#堆) + - [N叉堆](#n叉堆) + - [N叉索引堆](#n叉索引堆) +- [栈](#栈) +- [队列](#队列) +- [双端队列](#双端队列) +- [双向链表](#双向链表) +- [红黑树](#红黑树) +- [字典树](#字典树) +- [哈希表](#哈希表) +- [线段树](#线段树) +- [基准测试](#基准测试) + +### 动态数组 + +#### 去重 + +```go +package main + +import ( + "fmt" + "github.com/lxzan/dao/vector" +) + +func main() { + var v = vector.NewFromInts(1, 3, 5, 3) + v.Unique() + fmt.Printf("%v", v.Elem()) +} + +``` + +#### 排序 + +```go +package main + +import ( + "fmt" + "github.com/lxzan/dao/vector" +) + +func main() { + var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) + v.Sort() + fmt.Printf("%v", v.Elem()) +} + +``` + +#### 过滤 + +```go +package main + +import ( + "fmt" + "github.com/lxzan/dao/vector" +) + +func main() { + var v = vector.NewFromInts(1, 3, 5, 2, 4, 6) + v.Filter(func(i int, v vector.Int) bool { + return v.GetID()%2 == 0 + }) + fmt.Printf("%v", v.Elem()) +} + +``` + +### 堆 + +**堆** 又称之为优先队列, 堆顶元素总是最大或最小的. 常用的是四叉堆, `Push/Pop` 性能较为均衡. 使用 `y=pow(2,x)` 作为分叉数, +加快计算父子节点速度. + +#### N叉堆 + +```go +package main + +import ( + "github.com/lxzan/dao/heap" + "github.com/lxzan/dao/types/cmp" +) + +func main() { + var h = heap.NewWithWays(heap.Binary, cmp.Less[int]) + h.Push(1) + h.Push(3) + h.Push(5) + h.Push(2) + h.Push(4) + h.Push(6) + for h.Len() > 0 { + println(h.Pop()) + } +} + +``` + +#### N叉索引堆 + +在普通堆的基础上拓展了更新和删除功能, 常用于时间堆算法. + +```go +package main + +import ( + "github.com/lxzan/dao/heap" +) + +func main() { + var h = heap.NewIndexedHeap[int, struct{}](heap.Quadratic, func(a, b int) bool { return a > b }) + h.Push(1, struct{}{}) + h.Push(3, struct{}{}) + h.Push(5, struct{}{}) + h.Push(2, struct{}{}) + h.Push(4, struct{}{}) + h.Push(6, struct{}{}) + h.DeleteByIndex(5) + h.UpdateKeyByIndex(3, 7) + for h.Len() > 0 { + println(h.Pop().Key()) + } +} + +``` + +### 栈 + +**栈** 后进先出 (`LIFO`) 的数据结构 + +```go +package main + +import ( + "github.com/lxzan/dao/stack" +) + +func main() { + var s stack.Stack[int] + s.Push(1) + s.Push(3) + s.Push(5) + for s.Len() > 0 { + println(s.Pop()) + } +} +``` + +### 队列 + +**队列** 先进先出 (`FIFO`) 的数据结构. `dao/queue` 在全部元素弹出后会自动重置, 复用内存空间 + +```go +package main + +import ( + "github.com/lxzan/dao/queue" +) + +func main() { + var s = queue.New[int](0) + s.Push(1) + s.Push(3) + s.Push(5) + for s.Len() > 0 { + println(s.Pop()) + } +} + +``` + +### 双端队列 + +**双端队列** 类似于双向链表, 两端均可高效执行插入删除操作. + +`dao/deque` 基于数组下标模拟指针实现, 删除后的空间后续仍可复用, 且不依赖 `sync.Pool` + +```go +package main + +import ( + "fmt" + "github.com/lxzan/dao/deque" +) + +func main() { + var list = deque.New[int](8) + list.PushBack(1) + list.PushBack(3) + list.PushBack(5) + list.PushBack(7) + list.PushBack(9) + for i := list.Front(); i != nil; i = list.Get(i.Next()) { + fmt.Printf("%v ", i.Value()) + } + + println() + for i := list.Back(); i != nil; i = list.Get(i.Prev()) { + fmt.Printf("%v ", i.Value()) + } +} +``` + +### 双向链表 + +```go +package main + +import ( + "fmt" + "github.com/lxzan/dao/linkedlist" +) + +func main() { + var list = linkedlist.New[int]() + list.PushBack(1) + list.PushBack(3) + list.PushBack(5) + list.PushBack(7) + list.PushBack(9) + for i := list.Front(); i != nil; i = i.Next() { + fmt.Printf("%v ", i.Value) + } + + println() + for i := list.Back(); i != nil; i = i.Prev() { + fmt.Printf("%v ", i.Value) + } +} +``` + +### 红黑树 + +高性能红黑树实现, 可作为内存数据库使用. + +```go +package main + +import ( + "github.com/lxzan/dao/rbtree" +) + +func main() { + var tree = rbtree.New[int, struct{}]() + for i := 0; i < 10; i++ { + tree.Set(i, struct{}{}) + } + + var results = tree. + NewQuery(). + Left(func(key int) bool { return key >= 3 }). + Right(func(key int) bool { return key <= 5 }). + Order(rbtree.ASC). + FindAll() + for _, item := range results { + println(item.Key) + } +} + +``` + +### 字典树 + +**字典树** 又叫前缀树, 可以高效匹配字符串前缀. `dao/dict` 可以动态配置槽位宽度(由索引控制). + +注意: 合理设置索引, 超出索引长度的字符不能被索引优化 + +```go +package main + +import ( + "github.com/lxzan/dao/dict" +) + +func main() { + var tree = dict.New[int]() + tree.Set("listen", 1) + tree.Set("list", 2) + tree.Set("often", 3) + tree.Set("oh!", 4) + tree.Set("haha", 5) + tree.Set("", 6) + + tree.Match("list", func(key string, value int) bool { + println(key, value) + return true + }) +} +``` + +### 哈希表 + +`Runtime Map` 的别名, 拓展了 `Keys`, `Values`, `Range` 等实用方法. + +```go +package main + +import ( + "github.com/lxzan/dao/hashmap" +) + +func main() { + var m = hashmap.New[string, int](8) + m.Set("a", 1) + m.Set("b", 2) + m.Set("c", 3) + m.Range(func(key string, val int) bool { + println(key, val) + return true + }) +} +``` + +### 线段树 + +**线段树** 是一种二叉树,它的每个节点都表示一个区间。 线段树的特点是可以在`O(logn)`的时间内进行区间查询和区间更新。 + +```go +package main + +import ( + tree "github.com/lxzan/dao/segment_tree" +) + +func main() { + var data = []tree.Int64{1, 3, 5, 7, 9, 2, 4, 6, 8, 10} + var lines = tree.New[tree.Int64Schema, tree.Int64](data) + var result = lines.Query(0, 10) + println(result.MinValue, result.MaxValue, result.Sum) +} + +``` + +### 基准测试 + +- 1,000 elements + +``` +go test -benchmem -bench '^Benchmark' ./benchmark/ +goos: windows +goarch: amd64 +pkg: github.com/lxzan/dao/benchmark +cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics +BenchmarkDict_Set-12 8647 124.1 ns/op 48190 B/op 1003 allocs/op +BenchmarkDict_Get-12 9609 122.0 ns/op 48000 B/op 1000 allocs/op +BenchmarkDict_Match-12 3270 349.3 ns/op 48000 B/op 1000 allocs/op +BenchmarkHeap_Push_Binary-12 55809 21.0 ns/op 38912 B/op 3 allocs/op +BenchmarkHeap_Push_Quadratic-12 65932 18.0 ns/op 38912 B/op 3 allocs/op +BenchmarkHeap_Push_Octal-12 71005 16.1 ns/op 38912 B/op 3 allocs/op +BenchmarkHeap_Pop_Binary-12 10000 100.1 ns/op 16384 B/op 1 allocs/op +BenchmarkHeap_Pop_Quadratic-12 10000 100.7 ns/op 16384 B/op 1 allocs/op +BenchmarkHeap_Pop_Octal-12 9681 124.9 ns/op 16384 B/op 1 allocs/op +BenchmarkStdList_Push-12 24715 48.7 ns/op 54000 B/op 1745 allocs/op +BenchmarkStdList_PushAndPop-12 22006 54.7 ns/op 54000 B/op 1745 allocs/op +BenchmarkLinkedList_Push-12 38464 31.7 ns/op 24000 B/op 1000 allocs/op +BenchmarkLinkedList_PushAndPop-12 36898 32.6 ns/op 24000 B/op 1000 allocs/op +BenchmarkDeque_Push-12 100468 11.7 ns/op 24576 B/op 1 allocs/op +BenchmarkDeque_PushAndPop-12 51649 21.7 ns/op 37496 B/op 12 allocs/op +BenchmarkRBTree_Set-12 9999 113.9 ns/op 72048 B/op 2001 allocs/op +BenchmarkRBTree_Get-12 51806 22.7 ns/op 0 B/op 0 allocs/op +BenchmarkRBTree_FindAll-12 2808 421.3 ns/op 288001 B/op 5000 allocs/op +BenchmarkRBTree_FindAOne-12 4722 252.2 ns/op 56000 B/op 5000 allocs/op +BenchmarkSegmentTree_Query-12 7498 164.4 ns/op 20 B/op 0 allocs/op +BenchmarkSegmentTree_Update-12 10000 108.6 ns/op 15 B/op 0 allocs/op +BenchmarkSort_Quick-12 24488 48.5 ns/op 0 B/op 0 allocs/op +BenchmarkSort_Std-12 21703 54.9 ns/op 8216 B/op 2 allocs/op +PASS +ok github.com/lxzan/dao/benchmark 31.100s +``` + +- 1,000,000 elements + +``` +go test -benchmem -bench '^Benchmark' ./benchmark/ +goos: windows +goarch: amd64 +pkg: github.com/lxzan/dao/benchmark +cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics +BenchmarkDict_Set-12 1 2295.2 ns/op 1405087408 B/op 24868109 allocs/op +BenchmarkDict_Get-12 2 784.0 ns/op 48000000 B/op 1000000 allocs/op +BenchmarkDict_Match-12 2 961.0 ns/op 48000000 B/op 1000000 allocs/op +BenchmarkHeap_Push_Binary-12 48 24.8 ns/op 65708034 B/op 5 allocs/op +BenchmarkHeap_Push_Quadratic-12 58 19.4 ns/op 65708033 B/op 5 allocs/op +BenchmarkHeap_Push_Octal-12 69 17.1 ns/op 65708033 B/op 5 allocs/op +BenchmarkHeap_Pop_Binary-12 3 376.3 ns/op 16007168 B/op 1 allocs/op +BenchmarkHeap_Pop_Quadratic-12 3 342.8 ns/op 16007168 B/op 1 allocs/op +BenchmarkHeap_Pop_Octal-12 3 374.8 ns/op 16007168 B/op 1 allocs/op +BenchmarkStdList_Push-12 21 55.0 ns/op 55998007 B/op 1999745 allocs/op +BenchmarkStdList_PushAndPop-12 15 67.5 ns/op 55998008 B/op 1999745 allocs/op +BenchmarkLinkedList_Push-12 43 29.5 ns/op 24000000 B/op 1000000 allocs/op +BenchmarkLinkedList_PushAndPop-12 39 34.7 ns/op 24000002 B/op 1000000 allocs/op +BenchmarkDeque_Push-12 123 9.4 ns/op 24002560 B/op 1 allocs/op +BenchmarkDeque_PushAndPop-12 60 18.7 ns/op 45098876 B/op 37 allocs/op +BenchmarkRBTree_Set-12 6 171.9 ns/op 72000064 B/op 2000001 allocs/op +BenchmarkRBTree_Get-12 22 50.1 ns/op 0 B/op 0 allocs/op +BenchmarkRBTree_FindAll-12 1 1936.8 ns/op 288000128 B/op 5000001 allocs/op +BenchmarkRBTree_FindAOne-12 1 1793.4 ns/op 56000000 B/op 5000000 allocs/op +BenchmarkSegmentTree_Query-12 1 1630.0 ns/op 169678048 B/op 2000038 allocs/op +BenchmarkSegmentTree_Update-12 1 1025.0 ns/op 169678048 B/op 2000038 allocs/op +BenchmarkSort_Quick-12 10 109.5 ns/op 8003584 B/op 1 allocs/op +BenchmarkSort_Std-12 9 123.0 ns/op 8003608 B/op 2 allocs/op +PASS +ok github.com/lxzan/dao/benchmark 47.376s +``` diff --git a/benchmark/heap_test.go b/benchmark/heap_test.go index f8cedce..6521b53 100644 --- a/benchmark/heap_test.go +++ b/benchmark/heap_test.go @@ -8,7 +8,7 @@ import ( ) func BenchmarkHeap_Push_Binary(b *testing.B) { - var tpl = heap.NewWithForks(heap.Binary, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Binary, cmp.Less[int]) for j := 0; j < bench_count; j++ { tpl.Push(rand.Int()) } @@ -24,7 +24,7 @@ func BenchmarkHeap_Push_Binary(b *testing.B) { } func BenchmarkHeap_Push_Quadratic(b *testing.B) { - var tpl = heap.NewWithForks(heap.Quadratic, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Quadratic, cmp.Less[int]) for j := 0; j < bench_count; j++ { tpl.Push(rand.Int()) } @@ -39,7 +39,7 @@ func BenchmarkHeap_Push_Quadratic(b *testing.B) { } } func BenchmarkHeap_Push_Octal(b *testing.B) { - var tpl = heap.NewWithForks(heap.Octal, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Octal, cmp.Less[int]) for j := 0; j < bench_count; j++ { tpl.Push(rand.Int()) } @@ -55,7 +55,7 @@ func BenchmarkHeap_Push_Octal(b *testing.B) { } func BenchmarkHeap_Pop_Binary(b *testing.B) { - var tpl = heap.NewWithForks(heap.Binary, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Binary, cmp.Less[int]) for j := 0; j < bench_count*2; j++ { tpl.Push(rand.Int()) } @@ -70,7 +70,7 @@ func BenchmarkHeap_Pop_Binary(b *testing.B) { } func BenchmarkHeap_Pop_Quadratic(b *testing.B) { - var tpl = heap.NewWithForks(heap.Quadratic, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Quadratic, cmp.Less[int]) for j := 0; j < bench_count*2; j++ { tpl.Push(rand.Int()) } @@ -85,7 +85,7 @@ func BenchmarkHeap_Pop_Quadratic(b *testing.B) { } func BenchmarkHeap_Pop_Octal(b *testing.B) { - var tpl = heap.NewWithForks(heap.Octal, cmp.Less[int]) + var tpl = heap.NewWithWays(heap.Octal, cmp.Less[int]) for j := 0; j < bench_count*2; j++ { tpl.Push(rand.Int()) } diff --git a/hashmap/hashmap.go b/hashmap/hashmap.go index af56003..b4722d4 100644 --- a/hashmap/hashmap.go +++ b/hashmap/hashmap.go @@ -33,6 +33,12 @@ func (c HashMap[K, V]) Get(key K) (val V, exist bool) { return } +// Exists if key exists, return true +func (c HashMap[K, V]) Exists(key K) bool { + _, ok := c[key] + return ok +} + // Delete delete a element if the key exists func (c HashMap[K, V]) Delete(key K) { delete(c, key) diff --git a/hashmap/hashmap_test.go b/hashmap/hashmap_test.go index 471db56..d3fcaee 100644 --- a/hashmap/hashmap_test.go +++ b/hashmap/hashmap_test.go @@ -103,6 +103,8 @@ func TestHashMap_Range(t *testing.T) { m.Set("a", 1) m.Set("b", 2) m.Set("c", 3) + assert.True(t, m.Exists("a")) + assert.False(t, m.Exists("d")) var keys []string m.Range(func(key string, val int) bool { diff --git a/heap/heap.go b/heap/heap.go index d732c08..e93d747 100644 --- a/heap/heap.go +++ b/heap/heap.go @@ -1,6 +1,8 @@ package heap import ( + "github.com/lxzan/dao/algorithm" + "github.com/lxzan/dao/internal/utils" "github.com/lxzan/dao/types/cmp" ) @@ -14,20 +16,20 @@ const ( // New 新建一个最小四叉堆 // Create a new minimum quadratic heap -func New[T cmp.Ordered]() *Heap[T] { return NewWithForks(Quadratic, cmp.Less[T]) } +func New[T cmp.Ordered]() *Heap[T] { return NewWithWays(Quadratic, cmp.Less[T]) } -// NewWithForks 新建堆 -// @forks 分叉数, 可选值为: 2,4,6 +// NewWithWays 新建堆 +// @ways 分叉数, ways=pow(2,n) // @lessFunc 比较函数 -func NewWithForks[T any](forks uint32, lessFunc cmp.LessFunc[T]) *Heap[T] { +func NewWithWays[T any](ways uint32, lessFunc cmp.LessFunc[T]) *Heap[T] { h := &Heap[T]{lessFunc: lessFunc} - h.setForkNumber(forks) + h.setWays(ways) return h } type Heap[T any] struct { - bits uint32 - forks int + bits int + ways int data []T lessFunc func(a, b T) bool } @@ -38,17 +40,14 @@ func (c *Heap[T]) SetCap(n int) *Heap[T] { return c } -// setForkNumber 设置分叉数 -func (c *Heap[T]) setForkNumber(n uint32) *Heap[T] { - c.forks = int(n) - switch n { - case Quadratic, Binary: - c.bits = n / 2 - case Octal: - c.bits = 3 - default: - panic("incorrect number of forks") +// setWays 设置分叉数 +func (c *Heap[T]) setWays(n uint32) *Heap[T] { + n = algorithm.SelectValue(n == 0, Quadratic, n) + if !utils.IsBinaryNumber(n) { + panic("incorrect number of ways") } + c.ways = int(n) + c.bits = utils.GetBinaryExponential(c.ways) return c } @@ -66,29 +65,39 @@ func (c *Heap[T]) swap(i, j int) { } func (c *Heap[T]) up(i int) { - var j = (i - 1) >> c.bits - if i >= 1 && c.less(i, j) { + for i > 0 { + var j = (i - 1) >> c.bits + if !c.less(i, j) { + return + } + c.swap(i, j) - c.up(j) + i = j } } -func (c *Heap[T]) down(i, n int) { - var base = i << c.bits - var index = base + 1 - if index >= n { - return - } +func (c *Heap[T]) down(i int) { + var n = c.Len() + for { + var base = i << c.bits + var index = base + 1 + if index >= n { + return + } - for j := base + 2; j <= base+c.forks && j < n; j++ { - if c.less(j, index) { - index = j + var end = algorithm.Min(base+c.ways, n-1) + for j := base + 2; j <= end; j++ { + if c.less(j, index) { + index = j + } + } + + if !c.less(index, i) { + return } - } - if c.less(index, i) { c.swap(i, index) - c.down(index, n) + i = index } } @@ -115,7 +124,7 @@ func (c *Heap[T]) Pop() (ele T) { ele = c.data[0] c.data[0] = c.data[n-1] c.data = c.data[:n-1] - c.down(0, n-1) + c.down(0) } return } @@ -136,8 +145,7 @@ func (c *Heap[T]) Range(f func(index int, value T) bool) { func (c *Heap[T]) Clone() *Heap[T] { var v = *c - v.data = make([]T, len(c.data)) - copy(v.data, c.data) + v.data = utils.Clone(c.data) return &v } diff --git a/heap/heap_test.go b/heap/heap_test.go index f5e4344..294e360 100644 --- a/heap/heap_test.go +++ b/heap/heap_test.go @@ -36,7 +36,7 @@ func TestNew(t *testing.T) { } func TestDesc(t *testing.T) { - var h = NewWithForks(Octal, desc[int]) + var h = NewWithWays(Octal, desc[int]) h.SetCap(8) h.Push(1) assert.Equal(t, h.Top(), 1) @@ -53,7 +53,7 @@ func TestDesc(t *testing.T) { } func TestAsc(t *testing.T) { - var h = NewWithForks(Binary, cmp.Less[int]) + var h = NewWithWays(Binary, cmp.Less[int]) h.SetCap(8) h.Push(1) h.Push(3) @@ -69,7 +69,7 @@ func TestAsc(t *testing.T) { } func TestHeap_Range(t *testing.T) { - var h = NewWithForks(Quadratic, cmp.Less[int]) + var h = NewWithWays(Quadratic, cmp.Less[int]) h.SetCap(8) h.Push(1) h.Push(3) @@ -126,12 +126,12 @@ func TestHeap_SetForkNumber(t *testing.T) { } var err1 = catch(func() { - NewWithForks(3, cmp.Less[int]) + NewWithWays(3, cmp.Less[int]) }) assert.Error(t, err1) var err2 = catch(func() { - NewWithForks(4, cmp.Less[int]) + NewWithWays(4, cmp.Less[int]) }) assert.Nil(t, err2) } @@ -154,7 +154,7 @@ func TestHeap_Clone(t *testing.T) { } func TestHeap_UnWrap(t *testing.T) { - var h = NewWithForks(2, cmp.Less[int]) + var h = NewWithWays(2, cmp.Less[int]) h.Push(1) h.Push(2) h.Push(3) diff --git a/heap/index.go b/heap/index.go new file mode 100644 index 0000000..a7c8100 --- /dev/null +++ b/heap/index.go @@ -0,0 +1,210 @@ +package heap + +import ( + "github.com/lxzan/dao/algorithm" + "github.com/lxzan/dao/internal/utils" + "github.com/lxzan/dao/types/cmp" +) + +type ( + Element[K cmp.Ordered, V any] struct { + index int + key K + Value V + } + + IndexedHeap[K cmp.Ordered, V any] struct { + bits int + ways int + data []*Element[K, V] + lessFunc func(a, b K) bool + } +) + +// Key 获取排序Key +func (c *Element[K, V]) Key() K { + return c.key +} + +// Index 获取索引 +// index==-1 表示元素已被删除, 不允许做更新或删除操作 +func (c *Element[K, V]) Index() int { + return c.index +} + +func (c *Element[K, V]) delete() { + c.index = -1 +} + +// NewIndexedHeap 新建索引堆 +// @ways 分叉数, ways=pow(2,n) +// @lessFunc 比较函数, 可以传空指针, 默认为最小堆 +func NewIndexedHeap[K cmp.Ordered, V any](ways uint32, lessFunc cmp.LessFunc[K]) *IndexedHeap[K, V] { + var c = new(IndexedHeap[K, V]) + c.setWays(ways) + c.lessFunc = lessFunc + if c.lessFunc == nil { + c.lessFunc = cmp.Less[K] + } + return c +} + +// SetForkNumber 设置分叉数 +func (c *IndexedHeap[K, V]) setWays(n uint32) *IndexedHeap[K, V] { + n = algorithm.SelectValue(n == 0, Quadratic, n) + if !utils.IsBinaryNumber(n) { + panic("incorrect number of ways") + } + c.ways = int(n) + c.bits = utils.GetBinaryExponential(c.ways) + return c +} + +// Len 获取元素数量 +func (c *IndexedHeap[K, V]) Len() int { + return len(c.data) +} + +// Reset 重置堆 +func (c *IndexedHeap[K, V]) Reset() { + c.data = c.data[:0] +} + +// SetCap 设置预分配容量 +func (c *IndexedHeap[K, V]) SetCap(n int) *IndexedHeap[K, V] { + c.data = make([]*Element[K, V], 0, n) + return c +} + +func (c *IndexedHeap[K, V]) swap(i, j int) { + c.data[i].index, c.data[j].index = c.data[j].index, c.data[i].index + c.data[i], c.data[j] = c.data[j], c.data[i] +} + +func (c *IndexedHeap[K, V]) less(i, j int) bool { + return c.lessFunc(c.data[i].key, c.data[j].key) +} + +func (c *IndexedHeap[K, V]) up(i int) { + for i > 0 { + var j = (i - 1) >> c.bits + if !c.less(i, j) { + return + } + + c.swap(i, j) + i = j + } +} + +func (c *IndexedHeap[K, V]) down(i int) { + var n = c.Len() + for { + var base = i << c.bits + var index = base + 1 + if index >= n { + return + } + + var end = algorithm.Min(base+c.ways, n-1) + for j := base + 2; j <= end; j++ { + if c.less(j, index) { + index = j + } + } + + if !c.less(index, i) { + return + } + + c.swap(i, index) + i = index + } +} + +// Push 追加元素 +func (c *IndexedHeap[K, V]) Push(key K, value V) *Element[K, V] { + ele := &Element[K, V]{key: key, Value: value} + ele.index = c.Len() + c.data = append(c.data, ele) + c.up(c.Len() - 1) + return ele +} + +// Pop 弹出堆顶元素 +func (c *IndexedHeap[K, V]) Pop() (ele *Element[K, V]) { + var n = c.Len() + switch n { + case 0: + return ele + case 1: + ele = c.data[0] + c.data = c.data[:0] + default: + ele = c.data[0] + c.swap(0, n-1) + c.data = c.data[:n-1] + c.down(0) + } + ele.delete() + return ele +} + +// UpdateKeyByIndex 通过索引更新排序Key +func (c *IndexedHeap[K, V]) UpdateKeyByIndex(index int, key K) { + ele := c.data[index] + var down = c.lessFunc(ele.key, key) + ele.key = key + if down { + c.down(ele.index) + } else { + c.up(ele.index) + } +} + +// GetByIndex 通过索引获取元素 +func (c *IndexedHeap[K, V]) GetByIndex(index int) *Element[K, V] { + return c.data[index] +} + +// DeleteByIndex 通过索引删除元素 +func (c *IndexedHeap[K, V]) DeleteByIndex(index int) { + if index == 0 { + c.Pop() + return + } + + var n = c.Len() + var down = c.less(index, n-1) + c.swap(index, n-1) + c.data[n-1].delete() + c.data = c.data[:n-1] + if index < n-1 { + if down { + c.down(index) + } else { + c.up(index) + } + } +} + +// Top 获取堆顶元素 +func (c *IndexedHeap[K, V]) Top() *Element[K, V] { + return c.data[0] +} + +// Range 遍历 +func (c *IndexedHeap[K, V]) Range(f func(ele *Element[K, V]) bool) { + for _, v := range c.data { + if !f(v) { + return + } + } +} + +// Clone 拷贝索引堆副本 +func (c *IndexedHeap[K, V]) Clone() *IndexedHeap[K, V] { + var v = *c + v.data = utils.Clone(c.data) + return &v +} diff --git a/heap/index_test.go b/heap/index_test.go new file mode 100644 index 0000000..024833b --- /dev/null +++ b/heap/index_test.go @@ -0,0 +1,226 @@ +package heap + +import ( + "fmt" + "github.com/lxzan/dao/algorithm" + "github.com/lxzan/dao/internal/utils" + "github.com/lxzan/dao/types/cmp" + "github.com/stretchr/testify/assert" + "math/rand" + "testing" + "unsafe" +) + +func TestIndexedHeap_Sort(t *testing.T) { + var h = NewIndexedHeap[int, struct{}](Quadratic, nil) + for i := 0; i < 1000; i++ { + h.Push(rand.Int(), struct{}{}) + } + var arr []int + for h.Len() > 0 { + arr = append(arr, h.Pop().Key()) + } + assert.True(t, algorithm.IsSorted(arr, func(a, b int) int { + if a > b { + return 1 + } else if a < b { + return -1 + } else { + return 0 + } + })) +} + +func TestHeap_Random(t *testing.T) { + t.Run("asc", func(t *testing.T) { + const count = 10000 + var h = NewIndexedHeap[int, struct{}](Quadratic, cmp.Less[int]) + h.SetCap(count) + for i := 0; i < count; i++ { + flag := rand.Intn(5) + key := rand.Intn(count) + switch flag { + case 0, 1: + h.Push(key, struct{}{}) + case 2: + h.Pop() + case 3: + n := h.Len() + if n > 0 { + index := rand.Intn(n) + h.UpdateKeyByIndex(index, key) + } + case 4: + n := h.Len() + if n > 0 { + index := rand.Intn(n) + h.DeleteByIndex(index) + } + } + } + + for i, item := range h.data { + assert.Equal(t, item.Index(), i) + + if item.Index() == 0 { + item = h.Top() + } + var n = h.Len() + var base = i << h.bits + var end = algorithm.Min(base+h.ways, n-1) + for j := base + 1; j <= end; j++ { + assert.True(t, h.lessFunc(item.Key(), h.GetByIndex(j).Key())) + } + } + }) + + t.Run("desc", func(t *testing.T) { + const count = 10000 + var h = NewIndexedHeap[int, struct{}](Quadratic, func(a, b int) bool { + return a > b + }) + h.SetCap(count) + for i := 0; i < count; i++ { + flag := rand.Intn(5) + key := rand.Intn(count) + switch flag { + case 0, 1: + h.Push(key, struct{}{}) + case 2: + h.Pop() + case 3: + n := h.Len() + if n > 0 { + index := rand.Intn(n) + h.UpdateKeyByIndex(index, key) + } + case 4: + n := h.Len() + if n > 0 { + index := rand.Intn(n) + h.DeleteByIndex(index) + } + } + } + + for i, item := range h.data { + assert.Equal(t, item.Index(), i) + + if item.Index() == 0 { + item = h.Top() + } + var n = h.Len() + var base = i << h.bits + var end = algorithm.Min(base+h.ways, n-1) + for j := base + 1; j <= end; j++ { + assert.True(t, h.lessFunc(item.Key(), h.GetByIndex(j).Key())) + } + } + }) +} + +func TestIndexedHeap_Range(t *testing.T) { + var h = NewIndexedHeap[int, struct{}](Quadratic, cmp.Less[int]) + h.SetCap(8) + h.Push(1, struct{}{}) + h.Push(3, struct{}{}) + h.Push(2, struct{}{}) + h.Push(5, struct{}{}) + h.Push(4, struct{}{}) + + { + var arr []int + h.Range(func(ele *Element[int, struct{}]) bool { + arr = append(arr, ele.Key()) + return true + }) + assert.ElementsMatch(t, arr, []int{1, 2, 3, 4, 5}) + } + + { + var arr []int + h.Range(func(ele *Element[int, struct{}]) bool { + arr = append(arr, ele.Key()) + return len(arr) < 2 + }) + assert.Equal(t, len(arr), 2) + } +} + +func TestIndexedHeap_SetForkNumber(t *testing.T) { + var catch = func(f func()) (err error) { + defer func() { + if excp := recover(); excp != nil { + err = fmt.Errorf("%v", excp) + } + }() + f() + return err + } + + var err1 = catch(func() { + NewIndexedHeap[int, struct{}](3, cmp.Less[int]) + }) + assert.Error(t, err1) + + var err2 = catch(func() { + NewWithWays(4, cmp.Less[int]) + }) + assert.Nil(t, err2) +} + +func TestIndexedHeap_Clone(t *testing.T) { + var h = NewIndexedHeap[int, struct{}](4, nil) + h.Push(1, struct{}{}) + h.Push(3, struct{}{}) + h.Push(4, struct{}{}) + h.Push(4, struct{}{}) + + var h1 = h.Clone() + var h2 = h + assert.True(t, utils.IsSameSlice(h.data, h1.data)) + var addr = (uintptr)(unsafe.Pointer(&h.data[0])) + var addr1 = (uintptr)(unsafe.Pointer(&h1.data[0])) + var addr2 = (uintptr)(unsafe.Pointer(&h2.data[0])) + assert.NotEqual(t, addr, addr1) + assert.Equal(t, addr, addr2) + + h1.Reset() + assert.Equal(t, h1.Len(), 0) + assert.NotEqual(t, h2.Len(), 0) +} + +func TestIndexedHeap_DeleteByIndex(t *testing.T) { + t.Run("", func(t *testing.T) { + var h = NewIndexedHeap[int, string](Quadratic, cmp.Less[int]) + h.Push(1, "") + h.Push(2, "") + var ele = h.Push(3, "") + h.Push(4, "") + h.DeleteByIndex(ele.Index()) + assert.Equal(t, ele.Index(), -1) + var arr []int + h.Range(func(ele *Element[int, string]) bool { + arr = append(arr, ele.Key()) + return true + }) + assert.ElementsMatch(t, arr, []int{1, 2, 4}) + }) + + t.Run("", func(t *testing.T) { + var h = NewIndexedHeap[int, string](Quadratic, cmp.Less[int]) + h.Push(1, "") + h.Push(2, "") + h.Push(3, "") + h.Push(4, "") + var ele = h.Pop() + assert.Equal(t, ele.Index(), -1) + + var arr []int + h.Range(func(ele *Element[int, string]) bool { + arr = append(arr, ele.Key()) + return true + }) + assert.ElementsMatch(t, arr, []int{2, 3, 4}) + }) +} diff --git a/internal/utils/helper.go b/internal/utils/helper.go index 3795c59..f89f0b8 100644 --- a/internal/utils/helper.go +++ b/internal/utils/helper.go @@ -64,6 +64,16 @@ func IsBinaryNumber[T Integer](x T) bool { return x&(x-1) == 0 } +// GetBinaryExponential 获取指数 +func GetBinaryExponential(n int) int { + sum := 0 + for n > 1 { + n >>= 1 + sum++ + } + return sum +} + func Clone[S ~[]E, E any](s S) S { if s == nil { return nil diff --git a/internal/utils/helper_test.go b/internal/utils/helper_test.go index 0aa6490..96d47fd 100644 --- a/internal/utils/helper_test.go +++ b/internal/utils/helper_test.go @@ -54,6 +54,7 @@ func TestReverseStrings(t *testing.T) { } func TestIsBinaryNumber(t *testing.T) { + assert.True(t, IsBinaryNumber(0)) assert.True(t, IsBinaryNumber(1)) assert.True(t, IsBinaryNumber(2)) assert.True(t, IsBinaryNumber(16)) @@ -76,3 +77,12 @@ func TestClone(t *testing.T) { assert.ElementsMatch(t, b, a) } } + +func TestGetBinaryExponential(t *testing.T) { + assert.Equal(t, GetBinaryExponential(1), 0) + assert.Equal(t, GetBinaryExponential(2), 1) + assert.Equal(t, GetBinaryExponential(4), 2) + assert.Equal(t, GetBinaryExponential(8), 3) + assert.Equal(t, GetBinaryExponential(512), 9) + assert.Equal(t, GetBinaryExponential(1024), 10) +}