缘起

最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)
本系列笔记拟采用golang练习之

哈希表

  1. 哈希表存储的是由键(key)和值(value)组成的数据。
  2. 在哈希表中,我们可以利用哈希函数快速访问到数组中的目标数据。
  3. 如果发生哈希冲突(哈希函数对不同的键, 返回了相同的哈希值),
  4. 就使用链表进行存储(因此, 哈希表一般至少是数组+链表的二维结构)。
  5. 如果数组的空间太小,
  6. 使用哈希表的时候就容易发生冲突,
  7. 线性查找的使用频率也会更高;
  8. 反过来,如果数组的空间太大,
  9. 就会出现很多空箱子,造成内存的浪费。
  10. 摘自 <<我的第一本算法书>>(【日】石田保辉;宫崎修一)

目标

  • 实现一个哈希表, 提供对键值对数据的增删改查, 且性能不能太差
  • 物理结构
    • 采用分区 + 数组 + 链表的三维结构
    • 分区是一个双向链表, 由小到大呈2次幂增长
    • 当前分区总是指向最新也是最大的那个分区
    • 查找某个键时, 需要遍历所有分区
  • 哈希函数
    • 合适的哈希函数对性能影响非常巨大
    • 对哈希函数的要求一般是足够快, 足够”散”, 对不同键值最好能随机均匀分布
    • 本示例采用hash/crc64, 多项式为ECMA
    • 如果哈希函数的计算比较耗时, 则应当尽可能重复利用计算结果
  • 扩容与缩容
    • 扩容
      • 填充因子为3/4, 即当前分区的元素数>容量的3/4时, 创建2倍大的新分区
      • 扩容不做任何拷贝和rehash.
      • 扩容后, 非当前分区变成只读和只删.
    • 缩容: 当某个分区未持有任何元素, 且不为当前分区时, 删除此分区

设计

  • IHasher: 哈希支持接口, 定义哈希计算函数, 和键值相等比较函数
  • IMap: 哈希表接口
  • IMapIterator: 哈希表迭代器接口
  • tCrc64Hasher:
    • 基于hash/crc64, 实现IHasher接口.
    • 支持多种键类型
    • 对于整型的键, 返回自身;
    • 其他类型的键, 转为%v字符串再计算crc64的哈希值.
  • tHashMap: 哈希表的实现, 使用分区(双链)+数组+链表(单链)三维结构
  • tPartition: 哈希表分区. 其大小呈2次幂
  • tBucket: 哈希桶, 每个桶内的任何元素, 哈希值是一致的
  • tHashMapIterator: 哈希表迭代器的实现

单元测试

hashmap_test.go, 除基本功能测试, 还测试了百万键值插入+遍历的性能

  1. package data_structure
  2. import (
  3. "fmt"
  4. "learning/gooop/data_structure/hashmap"
  5. "strconv"
  6. "testing"
  7. "time"
  8. )
  9. func Test_HashMap(t *testing.T) {
  10. fnAssertTrue := func(b bool, msg string) {
  11. if !b {
  12. t.Fatal(msg)
  13. }
  14. }
  15. fnEquals := func(a interface{}, b interface{}) bool {
  16. i1,b1 := a.(int)
  17. if b1 {
  18. i2,b2 := b.(int)
  19. if b2 {
  20. return i1 == i2
  21. }
  22. }
  23. s1,b1 := a.(string)
  24. if b1 {
  25. s2,b2 := b.(string)
  26. if b2 {
  27. return s1 == s2
  28. }
  29. }
  30. return a == b
  31. }
  32. hasher := hashmap.NewCrc64Hashful(fnEquals)
  33. hm := hashmap.NewHashMap(hasher, 2)
  34. hm.Put(1, "10")
  35. t.Log(hm)
  36. fnAssertTrue(hm.Size() == 1, "expecting size == 1")
  37. fnAssertTrue(hm.IsNotEmpty(), "expecting not empty")
  38. ok,v := hm.Get(1)
  39. fnAssertTrue(ok, "expecting ok")
  40. fnAssertTrue(v == "10", "expecting 10")
  41. hm.Put(2, "20")
  42. hm.Put(3, "30")
  43. hm.Put(4, "40")
  44. hm.Put(5, "50")
  45. t.Log(hm)
  46. fnAssertTrue(hm.Size() == 5, "expecting size == 5")
  47. ok,v = hm.Get(2)
  48. fnAssertTrue(ok == true && v == "20", "expecting true and 20")
  49. hm.Clear()
  50. t.Log(hm)
  51. fnAssertTrue(hm.Size() == 0, "expecting size == 0")
  52. fnAssertTrue(hm.IsEmpty(), "expecting empty")
  53. iter := hm.Iterator()
  54. fnAssertTrue(!iter.More(), "expecting no more")
  55. hm.Put(1, "10")
  56. hm.Put(2, "20")
  57. hm.Put(3, "30")
  58. t.Log(hm)
  59. fnAssertTrue(hm.Has(1) && hm.Has(2) && hm.Has(3) && !hm.Has(4), "expecting has 1,2,3")
  60. hm.Put(4, "40")
  61. hm.Put(5, "50")
  62. hm.Put(6, "60")
  63. t.Log(hm)
  64. iter = hm.Iterator()
  65. fnAssertTrue(iter.More(), "expecting more")
  66. e,k,v := iter.Next()
  67. t.Logf("%v>%s", k, v)
  68. fnAssertTrue(e == nil, "e == nil")
  69. e,k,v = iter.Next()
  70. t.Logf("%v>%s", k, v)
  71. fnAssertTrue(e == nil, "e == nil")
  72. e,k,v = iter.Next()
  73. t.Logf("%v>%s", k, v)
  74. fnAssertTrue(e == nil, "e == nil")
  75. e,k,v = iter.Next()
  76. t.Logf("%v>%s", k, v)
  77. fnAssertTrue(e == nil, "e == nil")
  78. e,k,v = iter.Next()
  79. t.Logf("%v>%s", k, v)
  80. fnAssertTrue(e == nil, "e == nil")
  81. e,k,v = iter.Next()
  82. t.Logf("%v>%s", k, v)
  83. fnAssertTrue(e == nil, "e == nil")
  84. e,k,v = iter.Next()
  85. fnAssertTrue(e != nil, "expecting e != nil")
  86. ok,v = hm.Remove(3)
  87. t.Log(hm)
  88. fnAssertTrue(ok && v == "30" && hm.Size() == 5, "expecting remove 30")
  89. ok,v = hm.Remove(2)
  90. t.Log(hm)
  91. fnAssertTrue(ok && v == "20" && hm.Size() == 4, "expecting remove 20")
  92. t0 := time.Now().UnixNano()
  93. hm.Clear()
  94. size := 1000 * 1000
  95. for i := 0; i < size;i++ {
  96. hm.Put(strconv.Itoa(i), i)
  97. }
  98. millis := (time.Now().UnixNano() - t0) / 1000000
  99. t.Logf("putting %v string>int = %v ms", size, millis)
  100. fnAssertTrue(hm.Size() == size, fmt.Sprintf("expecting %v", size))
  101. for i := 0;i < size;i++ {
  102. ok, v = hm.Get(strconv.Itoa(i))
  103. fnAssertTrue(ok == true && v == i, "expecting i")
  104. }
  105. }

测试输出

  1. $ go test -v hashmap_test.go
  2. === RUN Test_HashMap
  3. hashmap_test.go:42: s=1,v=1 p[1:b[1 1]]
  4. hashmap_test.go:54: s=5,v=5 p[4:b[1 4],5:b[1 5]] p[1:b[1 1],2:b[1 2],3:b[1 3]]
  5. hashmap_test.go:60: s=0,v=6 p[]
  6. hashmap_test.go:70: s=3,v=9 p[1:b[1 1],2:b[1 2],3:b[1 3]]
  7. hashmap_test.go:76: s=6,v=12 p[1:b[1 1],2:b[1 2],3:b[1 3],4:b[1 4],5:b[1 5],6:b[1 6]]
  8. hashmap_test.go:80: 1>10
  9. hashmap_test.go:83: 2>20
  10. hashmap_test.go:86: 3>30
  11. hashmap_test.go:89: 4>40
  12. hashmap_test.go:92: 5>50
  13. hashmap_test.go:95: 6>60
  14. hashmap_test.go:101: s=5,v=13 p[1:b[1 1],2:b[1 2],4:b[1 4],5:b[1 5],6:b[1 6]]
  15. hashmap_test.go:105: s=4,v=14 p[1:b[1 1],4:b[1 4],5:b[1 5],6:b[1 6]]
  16. hashmap_test.go:115: putting 1000000 string>int = 1590 ms
  17. --- PASS: Test_HashMap (2.17s)
  18. PASS
  19. ok command-line-arguments 2.181s

IHasher.go

哈希支持接口, 定义哈希计算函数, 和键值相等比较函数

  1. package hashmap
  2. type IHasher interface {
  3. Hash(key interface{}) uint64
  4. Equals(a interface{}, b interface{}) bool
  5. }

IMap.go

哈希表接口

  1. package hashmap
  2. type IMap interface {
  3. Size() int
  4. IsEmpty() bool
  5. IsNotEmpty() bool
  6. Put(key interface{}, value interface{})
  7. Get(key interface{}) (bool,interface{})
  8. Has(key interface{}) bool
  9. Remove(key interface{}) (bool, interface{})
  10. Clear()
  11. Iterator() IMapIterator
  12. String() string
  13. }

IMapIterator.go

哈希表迭代器接口

  1. package hashmap
  2. type IMapIterator interface {
  3. More() bool
  4. Next() (err error, key interface{}, value interface{})
  5. }

tCrc64Hasher.go

  • 基于hash/crc64, 实现IHasher接口.
  • 支持多种键类型
  • 对于整型的键, 返回自身;
  • 其他类型的键, 转为%v字符串再计算crc64的哈希值. ```go package hashmap

import ( “fmt” “hash/crc64” )

var gCrc64Table = crc64.MakeTable(crc64.ECMA)

type FnEquals func(a interface{}, b interface{}) bool

type tCrc64Hasher struct { fnEquals FnEquals }

const INT_MAX = int(^uint(0) >> 1) const INT_MIN = ^INT_MAX const INT32_MAX = int32(^uint32(0) >> 1) const INT32_MIN = ^INT32_MAX const INT64_MAX = int64(^uint64(0) >> 1) const INT64_MIN = ^INT64_MAX

func (me *tCrc64Hasher) Hash(it interface{}) uint64 { if it == nil { return 0 }

  1. if v,ok := it.(int);ok {
  2. return uint64(v - INT_MIN)
  3. } else if v,ok := it.(int64);ok {
  4. return uint64(v - INT64_MIN)
  5. } else if v,ok := it.(int32);ok {
  6. return uint64(v - INT32_MIN)
  7. } else if v,ok := it.(uint32);ok {
  8. return uint64(v)
  9. } else if v,ok := it.(uint64);ok {
  10. return v
  11. } else if v,ok := it.(string);ok {
  12. return crc64.Checksum([]byte(v), gCrc64Table)
  13. } else {
  14. data := []byte(fmt.Sprintf("%v", it))
  15. return crc64.Checksum(data, gCrc64Table)
  16. }

}

func (me *tCrc64Hasher) Equals(a interface{}, b interface{}) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false }

  1. fn := me.fnEquals
  2. if fn == nil {
  3. return a == b
  4. } else {
  5. return fn(a, b)
  6. }

}

func NewCrc64Hashful(fn FnEquals) IHasher { return &tCrc64Hasher{ fnEquals: fn, } }

  1. <a name="4cl6J"></a>
  2. # tHashMap.go
  3. 哈希表的实现, 使用分区(双链)+数组+链表(单链)三维结构
  4. ```go
  5. package hashmap
  6. import (
  7. "fmt"
  8. "strings"
  9. )
  10. type tHashMap struct {
  11. hasher IHasher
  12. partitions *tPartition
  13. size int
  14. version int
  15. }
  16. func NewHashMap(hasher IHasher, capacity int) IMap {
  17. if capacity < 4 {
  18. capacity = 4
  19. }
  20. part := newPartition(hasher, capacity)
  21. return &tHashMap{
  22. hasher: hasher,
  23. partitions: part,
  24. size: 0,
  25. version: 0,
  26. }
  27. }
  28. func (me *tHashMap) Size() int {
  29. return me.size
  30. }
  31. func (me *tHashMap) IsEmpty() bool {
  32. return me.Size() <= 0
  33. }
  34. func (me *tHashMap) IsNotEmpty() bool {
  35. return !me.IsEmpty()
  36. }
  37. func (me *tHashMap) Put(key interface{}, value interface{}) {
  38. hash := me.hasher.Hash(key)
  39. ok, _, bucket, node, _ := me.findByKeyAndHash(key, hash)
  40. if ok {
  41. bucket.putAt(node, key, value)
  42. } else {
  43. if me.partitions.nearlyFull() {
  44. // create new partition
  45. part := newPartition(me.hasher, int(me.partitions.bucketCount * 2))
  46. part.next = me.partitions
  47. me.partitions.prev = part
  48. me.partitions = part
  49. part.appendByKeyAndHash(key, value, hash)
  50. } else {
  51. me.partitions.appendByKeyAndHash(key, value, hash)
  52. }
  53. me.size++
  54. }
  55. me.version++
  56. }
  57. func (me *tHashMap) findByKey(key interface{}) (ok bool, part *tPartition, bucket *tBucket, node *tLinkedNode, prev *tLinkedNode) {
  58. hash := me.hasher.Hash(key)
  59. return me.findByKeyAndHash(key, hash)
  60. }
  61. func (me *tHashMap) findByKeyAndHash(key interface{}, hash uint64) (ok bool, part *tPartition, bucket *tBucket, node *tLinkedNode, prev *tLinkedNode) {
  62. for part = me.partitions; part != nil; part = part.next {
  63. ok, bucket, node, prev = part.findByKeyAndHash(key, hash)
  64. if ok {
  65. return ok, part, bucket, node, prev
  66. }
  67. }
  68. return false, nil, nil, nil, nil
  69. }
  70. func (me *tHashMap) Get(key interface{}) (bool,interface{}) {
  71. ok, _, _, node, _ := me.findByKey(key)
  72. if ok {
  73. return true, node.value
  74. } else {
  75. return false, nil
  76. }
  77. }
  78. func (me *tHashMap) Has(key interface{}) bool {
  79. ok, _, _, _, _ := me.findByKey(key)
  80. return ok
  81. }
  82. func (me *tHashMap) Remove(key interface{}) (ok bool, value interface{}) {
  83. ok, part, bucket, node, prev := me.findByKey(key)
  84. if ok {
  85. value = node.value
  86. bucket.removeAt(node, prev)
  87. // 缩容
  88. if part.size <= 0 && part != me.partitions {
  89. me.removePartition(part)
  90. }
  91. me.size--
  92. me.version++
  93. return ok, value
  94. } else {
  95. return false, nil
  96. }
  97. }
  98. func (me *tHashMap) removePartition(part *tPartition) {
  99. prev := part.prev
  100. next := part.next
  101. if prev != nil {
  102. prev.next = next
  103. }
  104. if next != nil {
  105. next.prev = prev
  106. }
  107. if me.partitions == part {
  108. me.partitions = next
  109. }
  110. }
  111. func (me *tHashMap) Clear() {
  112. if me.IsEmpty() {
  113. return
  114. }
  115. part := newPartition(me.hasher, len(me.partitions.buckets))
  116. me.partitions = part
  117. me.size = 0
  118. me.version++
  119. }
  120. func (me *tHashMap) Iterator() IMapIterator {
  121. return newHashMapIterator(me)
  122. }
  123. func (me *tHashMap) String() string {
  124. itemStrings := make([]string, 0)
  125. for p := me.partitions;p != nil;p = p.next {
  126. itemStrings = append(itemStrings, p.String())
  127. }
  128. return fmt.Sprintf("s=%v,v=%v %s", me.size, me.version, strings.Join(itemStrings, " "))
  129. }

tPartition.go

哈希表分区. 其大小呈2次幂

  1. package hashmap
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type tPartition struct {
  7. hasher IHasher
  8. buckets []*tBucket
  9. bucketCount uint64
  10. prev *tPartition
  11. next *tPartition
  12. size int
  13. threshhold int
  14. }
  15. func newPartition(hasher IHasher, bucketCount int) *tPartition {
  16. it := &tPartition{
  17. hasher: hasher,
  18. buckets: make([]*tBucket, bucketCount),
  19. bucketCount: uint64(bucketCount),
  20. prev: nil,
  21. next: nil,
  22. size: 0,
  23. threshhold: bucketCount * 3 / 4,
  24. }
  25. for i,_ := range it.buckets {
  26. it.buckets[i] = newBucket(hasher)
  27. }
  28. return it
  29. }
  30. func (me *tPartition) putByKey(key interface{}, value interface{}) bool {
  31. hash := me.hasher.Hash(key)
  32. return me.putByKeyAndHash(key, value, hash)
  33. }
  34. func (me *tPartition) putByKeyAndHash(key interface{}, value interface{}, hash uint64) bool {
  35. if me.getBucketByHash(hash).put(key, value) {
  36. me.size++
  37. return true
  38. }
  39. return false
  40. }
  41. func (me *tPartition) appendByKeyAndHash(key interface{}, value interface{}, hash uint64) {
  42. me.getBucketByHash(hash).append(key, value)
  43. me.size++
  44. }
  45. func (me *tPartition) getBucketByKey(key interface{}) *tBucket {
  46. hash := me.hasher.Hash(key)
  47. return me.getBucketByHash(hash)
  48. }
  49. func (me *tPartition) getBucketByHash(hash uint64) *tBucket {
  50. return me.buckets[int(hash % me.bucketCount)]
  51. }
  52. func (me *tPartition) get(key interface{}) (bool,interface{}) {
  53. return me.getBucketByKey(key).get(key)
  54. }
  55. func (me *tPartition) findByKey(key interface{}) (ok bool,bucket *tBucket,node *tLinkedNode, prev *tLinkedNode) {
  56. bucket = me.getBucketByKey(key)
  57. ok,node,prev = bucket.find(key)
  58. return ok,bucket,node, prev
  59. }
  60. func (me *tPartition) findByKeyAndHash(key interface{}, hash uint64) (ok bool,bucket *tBucket,node *tLinkedNode, prev *tLinkedNode) {
  61. bucket = me.getBucketByHash(hash)
  62. ok,node,prev = bucket.find(key)
  63. return ok,bucket,node, prev
  64. }
  65. func (me *tPartition) remove(key interface{}) (bool, value interface{}) {
  66. ok,node := me.getBucketByKey(key).remove(key)
  67. if ok {
  68. me.size--
  69. return true, node.value
  70. } else {
  71. return false, nil
  72. }
  73. }
  74. func (me *tPartition) nearlyFull() bool {
  75. return me.size >= me.threshhold
  76. }
  77. func (me *tPartition) String() string {
  78. itemStrings := make([]string, 0)
  79. for i,b := range me.buckets {
  80. if b.size > 0 {
  81. itemStrings = append(itemStrings, fmt.Sprintf("%v:%s", i, b.String()))
  82. }
  83. }
  84. return fmt.Sprintf("p[%s]", strings.Join(itemStrings, ","))
  85. }

tBucket.go

哈希桶, 每个桶内的任何元素, 哈希值是一致的

  1. package hashmap
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. type tBucket struct {
  7. hasher IHasher
  8. nodes *tLinkedNode
  9. size int
  10. }
  11. type tLinkedNode struct {
  12. key interface{}
  13. value interface{}
  14. next *tLinkedNode
  15. }
  16. func newBucket(hasher IHasher) *tBucket {
  17. return &tBucket{
  18. hasher: hasher,
  19. nodes: nil,
  20. size: 0,
  21. }
  22. }
  23. func newLinkedNode(key interface{}, value interface{}) *tLinkedNode {
  24. return &tLinkedNode{
  25. key: key,
  26. value: value,
  27. next: nil,
  28. }
  29. }
  30. func (me *tBucket) put(key interface{}, value interface{}) bool {
  31. existed, node, _ := me.find(key)
  32. me.putAt(node, key, value)
  33. return !existed
  34. }
  35. func (me *tBucket) append(key interface{}, value interface{}) {
  36. me.putAt(nil, key, value)
  37. }
  38. func (me *tBucket) putAt(node *tLinkedNode, key interface{}, value interface{}) {
  39. if node != nil {
  40. node.value = value
  41. return
  42. }
  43. it := newLinkedNode(key, value)
  44. if me.nodes == nil {
  45. me.nodes = it
  46. } else {
  47. it.next = me.nodes
  48. me.nodes = it
  49. }
  50. me.size++
  51. }
  52. func (me *tBucket) get(key interface{}) (bool, interface{}) {
  53. ok, node, _ := me.find(key)
  54. if ok {
  55. return true, node.value
  56. }
  57. return false, nil
  58. }
  59. func (me *tBucket) find(key interface{}) (ok bool, node *tLinkedNode, prev *tLinkedNode) {
  60. prev = nil
  61. for it:=me.nodes;it != nil;it = it.next {
  62. if me.hasher.Equals(it.key, key) {
  63. return true, it, prev
  64. }
  65. prev = it
  66. }
  67. return false, nil, nil
  68. }
  69. func (me *tBucket) remove(key interface{}) (bool, *tLinkedNode) {
  70. ok,node, prev := me.find(key)
  71. if !ok {
  72. return false, nil
  73. }
  74. me.removeAt(node, prev)
  75. return true, node
  76. }
  77. func (me *tBucket) removeAt(node *tLinkedNode, prev *tLinkedNode) {
  78. if prev == nil {
  79. me.nodes = node.next
  80. } else {
  81. prev.next = node.next
  82. }
  83. me.size--
  84. }
  85. func (me *tBucket) String() string {
  86. itemStrings := make([]string, me.size)
  87. i := 0
  88. for it := me.nodes;it != nil;it = it.next {
  89. itemStrings[i] = fmt.Sprintf("%v", it.key)
  90. i++
  91. }
  92. return fmt.Sprintf("b[%v %s]", me.size, strings.Join(itemStrings, ","))
  93. }

tHashMapIterator.go

哈希表迭代器的实现

  1. package hashmap
  2. import "errors"
  3. type tHashMapIterator struct {
  4. hashMap *tHashMap
  5. pindex *tPartition
  6. bindex int
  7. nindex *tLinkedNode
  8. version int
  9. visited int
  10. }
  11. func newHashMapIterator(hashMap *tHashMap) IMapIterator {
  12. return &tHashMapIterator{
  13. hashMap: hashMap,
  14. pindex: hashMap.partitions,
  15. bindex: -1,
  16. nindex: nil,
  17. version: hashMap.version,
  18. visited: 0,
  19. }
  20. }
  21. func (me *tHashMapIterator) nextNode() *tLinkedNode {
  22. node := me.nindex
  23. for {
  24. if node == nil {
  25. bkt := me.nextBucket()
  26. if bkt == nil {
  27. return nil
  28. } else {
  29. me.nindex = bkt.nodes
  30. return me.nindex
  31. }
  32. } else {
  33. node = node.next
  34. if node != nil {
  35. me.nindex = node
  36. return node
  37. }
  38. }
  39. }
  40. }
  41. func (me *tHashMapIterator) nextBucket() *tBucket {
  42. part := me.pindex
  43. bi := me.bindex + 1
  44. for {
  45. if bi >= len(part.buckets) {
  46. part = me.nextPartition()
  47. if part == nil {
  48. return nil
  49. }
  50. bi = 0
  51. }
  52. bkt := part.buckets[bi]
  53. if bkt.nodes != nil {
  54. me.bindex = bi
  55. return bkt
  56. }
  57. bi++
  58. }
  59. }
  60. func (me *tHashMapIterator) nextPartition() *tPartition {
  61. if me.pindex == nil {
  62. return nil
  63. }
  64. me.pindex = me.pindex.next
  65. return me.pindex
  66. }
  67. func (me *tHashMapIterator) More() bool {
  68. if me.version != me.hashMap.version {
  69. return false
  70. }
  71. return me.visited < me.hashMap.size
  72. }
  73. func (me *tHashMapIterator) Next() (err error, key interface{}, value interface{}) {
  74. if me.version != me.hashMap.version {
  75. return gConcurrentModificationError, nil, nil
  76. }
  77. if !me.More() {
  78. return gNoMoreElementsError, nil, nil
  79. }
  80. node := me.nextNode()
  81. if node == nil {
  82. return gNoMoreElementsError, nil, nil
  83. } else {
  84. me.visited++
  85. return nil, node.key, node.value
  86. }
  87. }
  88. var gConcurrentModificationError = errors.New("concurrent modification error")
  89. var gNoMoreElementsError = errors.New("no more elements")

(end)