collections

package module
v0.0.0-...-e2a36f3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2019 License: MIT Imports: 4 Imported by: 0

README

📂 collctions

Golang 实现的 collections 模块,灵感来自 Python queuePython collections

Build Status Build status Go Report Card GoDoc

📚 目录

🔰 安装&引用
$ go get github.com/chenjiandongx/collections

import "github.com/chenjiandongx/collections"
📦 Collections
Queue

先进先出队列(线程安全)

📝 方法集

Get()(interface{}, bool)    // 出队
Put(v interface{})          // 入队
Qsize() int                 // 返回队列长度
IsEmpty() bool              // 判断队列是否为空

✏️ 示例

var nums = 1000

q := collections.NewQueue()
var item interface{}
var ok bool
for i := 0; i < nums; i++ {
    q.Put(i)
}
for i := 0; i < nums; i++ {
    if item, ok = q.Get(); ok {
        fmt.Println(item.(int))
    }
}

fmt.Println(q.IsEmpty())
fmt.Println(q.Qsize())
LifoQueue

后进先出队列(线程安全)

📝 方法集

Get()(interface{}, bool)    // 出队
Put(v interface{})          // 入队
Qsize() int                 // 返回队列长度
IsEmpty() bool              // 判断队列是否为空

✏️ 示例

var nums = 1000

q := collections.NewLifoQueue()
var item interface{}
var ok bool
for i := 0; i < nums; i++ {
    q.Put(i)
}
for i := nums-1; i >=0; i-- {
    if item, ok = q.Get(); ok {
        fmt.Println(item.(int))
    }
}

fmt.Println(q.IsEmpty())
fmt.Println(q.Qsize())
PriorityQueue

优先队列(线程安全)

📝 方法集

Get()(interface{}, bool)    // 出队
Put(v *PqNode)              // 入队
Qsize() int                 // 返回队列长度
IsEmpty() bool              // 判断队列是否为空

// 优先队列节点
type PqNode struct {
    Value           string
    Priority, index int
}

✏️ 示例

var nums = 1000

q := collections.NewPriorityQueue()

for i := 0; i < nums; i++ {
    r := rand.Int()
    q.Put(&collections.PqNode{Value: string(r), Priority: rand.Int()})
}

for i := 0; i < nums/2; i++ {
    item1, _ := q.Get()
    item2, _ := q.Get()
    fmt.Println(item1.(*collections.PqNode).Priority > item2.(*collections.PqNode).Priority)
}

fmt.Println(q.IsEmpty())
fmt.Println(q.Qsize())
Deque

双端队列(线程安全)

📝 方法集

GetLeft()(interface{}, bool)        // 左边出队
GetRight()(interface{}, bool)       // 右边出队
PutLeft(v interface{})              // 左边入队
PutRight(v interface{})             // 右边入队
Qsize() int                         // 返回队列长度
IsEmpty() bool                      // 判断队列是否为空

✏️ 示例

var nums = 1000
q := collections.NewDeque()

var item interface{}
var ok bool

for i := 0; i < nums; i++ {
    q.PutLeft(i)
}
fmt.Println(q.Qsize())

for i := nums - 1; i >= 0; i-- {
    q.PutRight(i)
}
fmt.Println(q.Qsize())

for i := 0; i < nums; i++ {
    item, ok = q.GetRight()
    fmt.Println(item, ok)
}
for i := nums - 1; i >= 0; i-- {
    item, ok = q.GetLeft()
    fmt.Println(item, ok)
}

item, ok = q.GetLeft()
fmt.Println(item, ok)

item, ok = q.GetRight()
fmt.Println(item, ok)
OrderedMap

有序 Map,接口设计参考 cevaris/ordered_map

📝 方法集

Set(key, value interface{})                 // 新增键值对
Get(key interface{}) (interface{}, bool)    // 取值
Delete(key interface{}) bool                // 删除键
Iter() (interface{}, interface{}, bool)     // 遍历
Len() int                                   // 键值对数量
// 指针回退到 Head,遍历时 current 指针会向后移动 BackToHead 使其移动到头指针,以便下一次从头遍历
BackToHead()                               

✏️ 示例

maxNum := 100
om := collections.NewOrderedMap()
for i := 0; i < maxNum; i++ {
    om.Set(i, i+1)
}

fmt.Println(om.Len())
om.Delete(0)
fmt.Println(om.Len())

for k, v, ok := om.Iter(); ok; k, v, ok = om.Iter() {
    fmt.Println(k, v)
}

om.BackToHead()
for k, v, ok := om.Iter(); ok; k, v, ok = om.Iter() {
    fmt.Println(k, v)
}

📣 讨论

有序 Map 在 Golang 中应该是十分常见的需求,Map 最大的优势就是其查找性能,理论上 Map 查找的时间复杂度为常数级。但实际情况我们可以通过 benchmark 来验证。在 Go Maps Don’t Appear to be O(1) 这篇文章中,作者测试了 Golang Map 查找的实际性能,不过作者是基于 Go1.4 的,版本有点旧了。下面是我修改了作者的测试案例后在 Go1.10 下跑出来的结果。

上图是使用 go-echarts 绘制的。测试通过与二分查找对比,二分查找的时间复杂度为 O(log2n)。很明显,在 10e5 数量级下两者的性能差别还不是特别大,主要差距是在 10e6 后体现的。结论:Map 的性能优于 O(log2n),但不是常数级。

collections.OrderdMap 🆚 cevaris/ordered_map

本来我一直使用的是 cevaris/ordered_map,后来自己重新实现了一个。实现完就与其进行了性能测试对比,它是基于两个 Map 实现的,而我是使用的 Map+LinkedList,LinkedList 在删除和插入操作上的时间复杂度都是 O(1),用其来存储 Map key 的顺序是一个很好的选择。

同样的测试代码,BenchMark 结果如下

goos: windows
goarch: amd64
pkg: github.com/chenjiandongx/collections
BenchmarkCollectionsSet-8        2000000               689 ns/op             187 B/op          3 allocs/op
BenchmarkCevarisSet-8            1000000              1212 ns/op             334 B/op          3 allocs/op
BenchmarkCollectionsGet-8        2000000               823 ns/op             187 B/op          3 allocs/op
BenchmarkCevarisGet-8            1000000              1281 ns/op             334 B/op          3 allocs/op
BenchmarkCollectionsIter-8       2000000               670 ns/op             187 B/op          3 allocs/op
BenchmarkCevarisIter-8           1000000              1341 ns/op             366 B/op          4 allocs/op

collections.OrderedMap Win 🖖 性能+内存占用全部占优 🚀

Counter

计数器

📝 方法集

// key-value item
type Item struct {
    k interface{}
    v int
}

Add(keys ...interface{})            // 新增 item
Get(key interface{}) int            // 获取 key 计数
GetAll() []Item                     // 获取全部 key 计数
Top(n int) []Item                   // 获取前 key 计数
Delete(key interface{}) bool        // 删除 key,成功返回 true,key 不存在返回 false
Len() int                           // key 数量

✏️ 示例

c := collections.NewCounter()
c.Add("a", "b", "c", "d", "a", "c")
fmt.Println(c.Get("A"))
fmt.Println(c.Get("a"))
fmt.Println(c.Get("b"))
fmt.Println(c.Top(2))
fmt.Println(c.Len())
fmt.Println(c.All())
c.Delete("a")
AVLTree

AVL 二叉自平衡查找树

📝 方法集

NewAVLTree() *AVLTree       // 生成 AVL 树
Insert(v int)               // 插入节点
Search(v int) bool          // 搜索节点
Delete(v int) bool          // 删除节点
GetMaxValue() int           // 获取所有节点中的最大值
GetMinValue() int           // 获取所有节点中的最小值
AllValues() []int           // 返回排序后所有值

✏️ 示例

var maxNum = 100

tree := NewAVLTree()
for i := 0; i < maxNum; i++ {
    tree.Insert(i)
    tree.Insert(maxNum + i)
}
fmt.Println(len(tree.AllValues()))
fmt.Println(tree.GetMaxValue())
fmt.Println(tree.GetMinValue())
fmt.Println(tree.Search(50))
fmt.Println(tree.Search(100))
fmt.Println(tree.Search(-10))
fmt.Println(tree.Delete(-10))
fmt.Println(tree.Delete(10))

📣 讨论

AVL 树是自平衡树的一种,其通过左旋和右旋来调整自身的平衡性,使其左右子树的高度差最大不超过 1。AVL 在插入、查找、删除的平时时间复杂度都是 O(logn),在基本的 BST(二叉查找树)中,理想情况的效率也是为 O(logn),但由于操作的性能其实是依赖于树的高度,BST 最坏的情况会导致树退化成链表,此时时间复杂度就变为 O(n),为了解决这个问题,自平衡二叉树应运而生。

AVL 的主要精髓在于旋转,旋转分为 4 种情况,左旋,左旋+右旋,右旋,右旋+左旋。调整树结构后需要重新计算树高。

左子树左节点失衡

左左情况 直接右旋

    x                
  x        => 右旋         x
x                       x    x

左子树右节点失衡

左右情况 先左旋后右旋

  x                        x     
x         => 左旋         x       => 右旋        x
  x                     x                     x    x

右子树右节点失衡

右右情况 直接左旋

x                
  x       => 左旋          x
    x                   x    x

右子树左节点失衡

右左情况 先右旋后左旋

x                      x     
  x       => 右旋        x       => 左旋        x
x                          x                 x    x

AVL 主要的性能消耗主要在插入,因为其需要通过旋转来维护树的平衡,但如果使用场景是经常需要排序和查找数据的话,AVL 还是可以展现其良好的性能的。

benchmark

BenchmarkAVLInsert10e1-6        2000000000               0.00 ns/op
BenchmarkAVLInsert10e2-6        2000000000               0.00 ns/op
BenchmarkAVLInsert10e3-6        2000000000               0.00 ns/op
BenchmarkAVLInsert10e4-6        2000000000               0.02 ns/op
BenchmarkAVLInsert10e5-6        1000000000               0.82 ns/op
BenchmarkAVLSearch-6            2000000000               0.00 ns/op
BenchmarkAVLDelete-6            2000000000               0.00 ns/op
Sort

📝 方法集

BubbleSort()        // 冒泡排序
InsertionSort()     // 插入排序
QuickSort()         // 快速排序
ShellSort()         // 希尔排序
HeapSort()          // 堆排序
MergeSort()         // 归并排序

✏️ 示例

var maxCnt = 10e4

func yieldRandomArray() []int {
    res := make([]int, maxCnt)
    for i := 0; i < maxCnt; i++ {
        res[i] = rand.Int()
    }
    return res
}

BubbleSort(yieldRandomArray())
InsertionSort(yieldRandomArray())
QuickSort(yieldRandomArray())
ShellSort(yieldRandomArray())
HeapSort(yieldRandomArray())
MergeSort(yieldRandomArray())

📣 讨论

排序算法时间复杂度比较

排序算法 是否稳定 平均 最好 最差 动画演示
BubbleSort O(n^2) O(n) O(n^2)
InsertionSort O(n^2) O(n) O(n^2)
QuickSort O(nlogn) O(nlogn) O(n^2)
ShellSort O(nlogn) O(n) O(n^2)
HeapSort O(nlogn) O(nlogn) O(nlogn)
MergeSort O(nlogn) O(nlogn) O(nlogn)

通过 benchmark 来测试平均排序性能

数据随机分布

var maxCnt int = 10e4

func yieldRandomArray(cnt int) []int {
    res := make([]int, cnt)
    for i := 0; i < cnt; i++ {
        res[i] = rand.Int()
    }
    return res
}

运行结果

BenchmarkBubbleSort-8                  1        17361549400 ns/op
BenchmarkInsertionSort-8               1        1934826900 ns/op
BenchmarkQuickSort-8                 100          10651807 ns/op
BenchmarkShellSort-8                 100          16476199 ns/op
BenchmarkHeapSort-8                  100          14231607 ns/op
BenchmarkMergeSort-8                 100          14840583 ns/op

冒泡和直接插入排序在随机数据集的排序性能最差,为 O(n^2),剩余 4 种排序快排效率最佳,其他 3 者性能很接近。

换两种极端的数据分布方式

数据升序分布

func yieldArrayAsce(cnt int) []int {
    res := make([]int, cnt)
    for i := 0; i < cnt; i++ {
        res[i] = i
    }
    return res
}

运行结果

BenchmarkBubbleSort-8               5000            266690 ns/op
BenchmarkInsertionSort-8           10000            213429 ns/op
BenchmarkQuickSort-8                   1        3291222900 ns/op
BenchmarkShellSort-8                1000           1716406 ns/op
BenchmarkHeapSort-8                  200           6806788 ns/op
BenchmarkMergeSort-8                 300           4677485 ns/op

在数据基本升序的情况下,冒泡和直接插入排序能够取得良好的性能。而快排就给跪了,就是最差的 O(n^2) 了。

数据降序分布

func yieldArrayDesc(cnt int) []int {
    res := make([]int, cnt)
    for i := 0; i < cnt; i++ {
        res[i] = cnt-i
    }
    return res
}

运行结果

BenchmarkBubbleSort-8                  1        6710048800 ns/op
BenchmarkInsertionSort-8               1        3881599100 ns/op
BenchmarkQuickSort-8                   1        3373971200 ns/op
BenchmarkShellSort-8                 500           2876371 ns/op
BenchmarkHeapSort-8                  200           7081150 ns/op
BenchmarkMergeSort-8                 300           4448222 ns/op

在数据基本降序的情况下,冒泡和直接插入排序一如既往的差,快排又给跪了,又是 O(n^2)...

那自己实现的排序和 Golang 官方提供的 sort.Sort 排序方法对比,效率如何呢

定义一个 struct,实现 sort.Interface

import "sort"

type StdItems struct {
    data []int
}

func (o StdItems) Less(i, j int) bool {
    return o.data[i] < o.data[j]
}

func (o StdItems) Swap(i, j int) {
    o.data[i], o.data[j] = o.data[j], o.data[i]
}

func (o StdItems) Len() int {
    return len(o.data)
}

只取 n(logn) 复杂度的排序算法与标准 sort 进行对比

数据随机分布

BenchmarkStdSort-8                            50          22978524 ns/op
BenchmarkQuickSort-8                         100          11648689 ns/op
BenchmarkShellSort-8                         100          17353544 ns/op
BenchmarkHeapSort-8                          100          14501199 ns/op
BenchmarkMergeSort-8                         100          13793086 ns/op

是不是眼前一亮 😂,自己写的快排居然这么厉害,比标准的 sort 快了不止两倍??? 这里出现这种情况的主要原因是 sort 实现了 sort.Interface,该接口需要有三个方法 Less()/Len()/Swap(),而接口的类型转换是有成本的。通用意味着兼容,兼容意味着妥协,这是专和精权衡后的结果。当然,标准的 sort 大部分情况的性能都是可接受的。但当你需要追求极致性能的话,自己针对特定需求实现排序算法肯定会是更好的选择。

数据升序分布

BenchmarkStdSort-8                           200           7285511 ns/op
BenchmarkQuickSort-8                           1        3351046900 ns/op
BenchmarkShellSort-8                        1000           1679506 ns/op
BenchmarkHeapSort-8                          200           6632256 ns/op
BenchmarkMergeSort-8                         300           4308582 ns/op

是不是又是眼前一亮 🤣,我去 为什么这次标准的排序比快排快了这么多,官方的排序不也是快排吗?(这个测试结果看起来好像也没人会比快排慢是吧 😅)

数据降序分布

BenchmarkStdSort-8                           200           7405331 ns/op
BenchmarkQuickSort-8                           1        3390954400 ns/op
BenchmarkShellSort-8                         500           2900240 ns/op
BenchmarkHeapSort-8                          200           7091124 ns/op
BenchmarkMergeSort-8                         300           4295169 ns/op

emmmmmmm,同上 😓

关于官方排序的具体实现,可以参考 src/sort/sort.go,实际上是直接插入排序,快速排序,堆排序和归并排序的组合排序。这篇文章 对这部分有介绍

最后,按官方的排序针对自己想要的数据类型排序,但不使用接口那套,对比上面排序中最快的算法以及接口实现的 sort

数据随机分布

BenchmarkStdSort-8                           100          22649399 ns/op
BenchmarkQuickSort-8                         100          10870924 ns/op
BenchmarkStdSortWithoutInterface-8           100          10511605 ns/op

数据升序分布

BenchmarkStdSort-8                           200           7006117 ns/op
BenchmarkShellSort-8                        1000           1667537 ns/op
BenchmarkStdSortWithoutInterface-8          1000           1619643 ns/op

数据降序分布

BenchmarkStdSort-8                           200           7614625 ns/op
BenchmarkShellSort-8                         500           3051834 ns/op
BenchmarkStdSortWithoutInterface-8          1000           1689479 ns/op

🖖 StdSortWithoutInterface 完胜!!!

我们还可以进一步思考如何获得更高的排序性能,使用 goroutine 将一个数据切分成两半,分别使用 StdSortWithoutInterface 排序,将排序后的结果进行一次归并排序,就可以得到最终的有序数组,这次我们测试的数组长度为 10e5

为了验证真正的并行计算 我们将分别测试 cpu 数量为 1, 2, 8 的情况

BenchmarkStdSort                               5         260696480 ns/op
BenchmarkStdSort-2                             5         246746560 ns/op
BenchmarkStdSort-8                             5         248532560 ns/op
BenchmarkStdSortWithoutInterface              10         124666470 ns/op
BenchmarkStdSortWithoutInterface-2            10         120676740 ns/op
BenchmarkStdSortWithoutInterface-8            10         126062650 ns/op
BenchmarkStdSortWithGoroutine                 20         125163280 ns/op
BenchmarkStdSortWithGoroutine-2               20          80835825 ns/op
BenchmarkStdSortWithGoroutine-8               20          81232625 ns/op

😎 WOW!!! cpu 数量为 1 时大家相差无几,cpu > 1 以后,goroutine 做到了真正的并行,利用多核进行计算,速度提升了 1.5 倍,比默认的 Sort 方法提升了 4 倍。喏,这就是算法的魅力。

📃 License

MIT ©chenjiandongx

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BubbleSort

func BubbleSort(items []int)

冒泡排序:稳定 平均 O(n^2) 最好 O(n) 最坏 O(n^2) https://upload.wikimedia.org/wikipedia/commons/3/37/Bubble_sort_animation.gif

func HeapSort

func HeapSort(items []int)

堆排序:不稳定 平均 O(nlogn) 最好 O(nlogn) 最坏 O(nlogn) https://upload.wikimedia.org/wikipedia/commons/1/1b/Sorting_heapsort_anim.gif

func InsertionSort

func InsertionSort(items []int)

直接插入排序:稳定 平均 O(n^2) 最好 O(n) 最差 O(n^2) https://upload.wikimedia.org/wikipedia/commons/2/25/Insertion_sort_animation.gif

func MergeSort

func MergeSort(items []int)

归并排序:稳定 平均 O(nlogn) 最好 O(nlogn) 最差 O(nlogn) https://upload.wikimedia.org/wikipedia/commons/c/c5/Merge_sort_animation2.gif

func QuickSort

func QuickSort(items []int)

快速排序:不稳定 平均 O(nlogn) 最好 O(nlogn) 最坏 O(n^2) https://upload.wikimedia.org/wikipedia/commons/6/6a/Sorting_quicksort_anim.gif

func ShellSort

func ShellSort(items []int)

希尔排序 不稳定 平均 O(nlogn) 最好 O(n) 最坏 O(n^2) https://upload.wikimedia.org/wikipedia/commons/d/d8/Sorting_shellsort_anim.gif

func StdSortWithGoroutine

func StdSortWithGoroutine(data []int)

func StdSortWithoutInterface

func StdSortWithoutInterface(data []int)

Types

type AVLTree

type AVLTree struct {
	// contains filtered or unexported fields
}

func NewAVLTree

func NewAVLTree() *AVLTree

生成 AVL 树

func (*AVLTree) AllValues

func (a *AVLTree) AllValues() []int

返回排序后所有值

func (*AVLTree) Delete

func (a *AVLTree) Delete(v int) bool

删除节点

func (*AVLTree) GetMaxValue

func (a *AVLTree) GetMaxValue() int

获取所有节点中的最大值

func (*AVLTree) GetMinValue

func (a *AVLTree) GetMinValue() int

获取所有节点中的最小值

func (*AVLTree) Insert

func (a *AVLTree) Insert(v int)

插入节点

func (*AVLTree) Search

func (a *AVLTree) Search(v int) bool

搜索节点

type Counter

type Counter struct {
	// contains filtered or unexported fields
}

func NewCounter

func NewCounter() *Counter

func (*Counter) Add

func (c *Counter) Add(keys ...interface{})

func (*Counter) Delete

func (c *Counter) Delete(key interface{}) bool

func (*Counter) Get

func (c *Counter) Get(key interface{}) int

func (*Counter) GetAll

func (c *Counter) GetAll() []Item

func (*Counter) Len

func (c *Counter) Len() int

func (*Counter) Top

func (c *Counter) Top(n int) []Item

type Deque

type Deque struct {
	// contains filtered or unexported fields
}

func NewDeque

func NewDeque() *Deque

func (*Deque) GetLeft

func (q *Deque) GetLeft() (interface{}, bool)

func (*Deque) GetRight

func (q *Deque) GetRight() (interface{}, bool)

func (*Deque) IsEmpty

func (q *Deque) IsEmpty() bool

func (*Deque) PutLeft

func (q *Deque) PutLeft(v interface{})

func (*Deque) PutRight

func (q *Deque) PutRight(v interface{})

func (*Deque) Qsize

func (q *Deque) Qsize() int

type Item

type Item struct {
	// contains filtered or unexported fields
}

type LifoQueue

type LifoQueue struct {
	// contains filtered or unexported fields
}

func NewLifoQueue

func NewLifoQueue() *LifoQueue

func (*LifoQueue) Get

func (q *LifoQueue) Get() (interface{}, bool)

func (*LifoQueue) IsEmpty

func (q *LifoQueue) IsEmpty() bool

func (*LifoQueue) Put

func (q *LifoQueue) Put(v interface{})

func (*LifoQueue) Qsize

func (q *LifoQueue) Qsize() int

type OrderedMap

type OrderedMap struct {
	// contains filtered or unexported fields
}

func NewOrderedMap

func NewOrderedMap() *OrderedMap

func (*OrderedMap) BackToHead

func (om *OrderedMap) BackToHead()

func (*OrderedMap) Delete

func (om *OrderedMap) Delete(key interface{}) bool

func (*OrderedMap) Get

func (om *OrderedMap) Get(key interface{}) (interface{}, bool)

func (*OrderedMap) Iter

func (om *OrderedMap) Iter() (interface{}, interface{}, bool)

func (*OrderedMap) Len

func (om *OrderedMap) Len() int

func (*OrderedMap) Set

func (om *OrderedMap) Set(key, value interface{})

type PqNode

type PqNode struct {
	Value    string
	Priority int
	// contains filtered or unexported fields
}

PriorityQueue Node

type PriorityQueue

type PriorityQueue struct {
	// contains filtered or unexported fields
}

func NewPriorityQueue

func NewPriorityQueue() *PriorityQueue

func (*PriorityQueue) Get

func (pq *PriorityQueue) Get() (interface{}, bool)

func (*PriorityQueue) IsEmpty

func (pq *PriorityQueue) IsEmpty() bool

func (PriorityQueue) Len

func (pq PriorityQueue) Len() int

`Sort` interface Len()

func (PriorityQueue) Less

func (pq PriorityQueue) Less(i, j int) bool

`Sort` interface Less()

func (*PriorityQueue) Pop

func (pq *PriorityQueue) Pop() interface{}

`Heap` interface Pop()

func (*PriorityQueue) Push

func (pq *PriorityQueue) Push(v interface{})

`Heap` interface Push()

func (*PriorityQueue) Put

func (pq *PriorityQueue) Put(v *PqNode)

func (PriorityQueue) Qsize

func (pq PriorityQueue) Qsize() int

func (PriorityQueue) Swap

func (pq PriorityQueue) Swap(i, j int)

`Sort` interface Swap()

type Queue

type Queue struct {
	// contains filtered or unexported fields
}

func NewQueue

func NewQueue() *Queue

func (*Queue) Get

func (q *Queue) Get() (interface{}, bool)

func (*Queue) IsEmpty

func (q *Queue) IsEmpty() bool

func (*Queue) Put

func (q *Queue) Put(v interface{})

func (*Queue) Qsize

func (q *Queue) Qsize() int

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL