Skip to content

Commit 472c51e

Browse files
committed
separate indexmap and arraymap
1 parent e85c798 commit 472c51e

File tree

3 files changed

+213
-40
lines changed

3 files changed

+213
-40
lines changed

ds/arraymap.go

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,77 @@ package ds
22

33
import (
44
"iter"
5-
6-
"golang.org/x/exp/constraints"
75
)
86

9-
// Acts like a map, but backed by an integer indexed slice instead of a map
10-
// This is mostly for use cases where you have a small list of increasing numbers that you want to store in an array, but you dont want to worry about ensuring the bounds are always correct
11-
// Note: If you put a huge key in here, the slice will allocate a ton of space.
12-
type ArrayMap[K constraints.Integer, V any] struct {
13-
// TODO: set slice should just be a bitmask
14-
set []bool // Tracks whether or not the data at a location is set or empty
15-
slice []V // Tracks the data
7+
type arrayMapData[K comparable, V any] struct {
8+
key K
9+
value V
10+
}
11+
func newArrayMapData[K comparable, V any](k K, v V) arrayMapData[K, V] {
12+
return arrayMapData[K, V]{
13+
key: k,
14+
value: v,
15+
}
16+
}
17+
18+
// Acts like a map, but is backed by an array. Can provide better iteration speed at the cost of slower lookups
19+
type ArrayMap[K comparable, V any] struct {
20+
slice []arrayMapData[K, V] // TODO: Maybe faster with two slices? one for key, another for value?
1621
}
1722

18-
func NewArrayMap[K constraints.Integer, V any]() ArrayMap[K, V] {
23+
func NewArrayMap[K comparable, V any]() ArrayMap[K, V] {
1924
return ArrayMap[K, V]{
20-
set: make([]bool, 0),
21-
slice: make([]V, 0),
25+
slice: make([]arrayMapData[K, V], 0),
2226
}
2327
}
2428

25-
func(m *ArrayMap[K, V]) grow(idx K) {
26-
requiredLength := idx + 1
27-
growAmount := requiredLength - K(len(m.set))
28-
if growAmount <= 0 {
29-
return // No need to grow if the sliceIdx is already in bounds
29+
func (m *ArrayMap[K, V]) append(key K, val V) {
30+
m.slice = append(m.slice, newArrayMapData(key, val))
31+
}
32+
33+
// returns the index of the value, or -1 if the key does not exist
34+
func (m *ArrayMap[K, V]) find(key K) int {
35+
for i := range m.slice {
36+
if m.slice[i].key != key {
37+
continue // Skip: Wrong key
38+
}
39+
40+
return i
3041
}
3142

32-
m.set = append(m.set, make([]bool, growAmount)...)
33-
m.slice = append(m.slice, make([]V, growAmount)...)
43+
return -1
3444
}
3545

36-
func(m *ArrayMap[K, V]) Put(idx K, val V) {
46+
func (m *ArrayMap[K, V]) Put(key K, val V) {
47+
idx := m.find(key)
3748
if idx < 0 {
38-
return
49+
// Can't find key, so just append
50+
m.append(key, val)
51+
} else {
52+
// Else, just update the current key
53+
m.slice[idx].value = val
3954
}
40-
41-
m.grow(idx) // Ensure index is within bounds
42-
43-
m.set[idx] = true
44-
m.slice[idx] = val
4555
}
4656

47-
func(m *ArrayMap[K, V]) Get(idx K) (V, bool) {
48-
if idx < 0 || idx >= K(len(m.set)) {
57+
func (m *ArrayMap[K, V]) Get(key K) (V, bool) {
58+
idx := m.find(key)
59+
if idx < 0 {
4960
var v V
5061
return v, false
62+
} else {
63+
return m.slice[idx].value, true
5164
}
52-
53-
return m.slice[idx], m.set[idx]
5465
}
5566

56-
// Delete a specific index
57-
func(m *ArrayMap[K, V]) Delete(idx K) {
58-
m.set[idx] = false
67+
// Delete a specific index. Note this will move the last index into the hole
68+
func (m *ArrayMap[K, V]) Delete(key K) {
69+
idx := m.find(key)
70+
if idx < 0 {
71+
return // Nothing to do, does not exist
72+
}
73+
74+
m.slice[idx] = m.slice[len(m.slice)-1]
75+
m.slice = m.slice[:len(m.slice)-1]
5976
}
6077

6178
// Clear the entire slice
@@ -66,13 +83,8 @@ func(m *ArrayMap[K, V]) Clear() {
6683
// Iterate through the entire slice
6784
func(m *ArrayMap[K, V]) All() iter.Seq2[K, V] {
6885
return func(yield func(K, V) bool) {
69-
for i, v := range m.slice {
70-
// Ensure that the map key is set
71-
if !m.set[i] {
72-
continue
73-
}
74-
75-
if !yield(K(i), v) {
86+
for _, d := range m.slice {
87+
if !yield(d.key, d.value) {
7688
break
7789
}
7890
}

ds/arraymap_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,87 @@ func TestArrayMap(t *testing.T) {
3030
compare(t, v, "")
3131

3232

33+
// Add and check 150 (outside current bounds)
34+
m.Put(150, "150")
35+
v, ok = m.Get(150)
36+
check(t, ok)
37+
compare(t, v, "150")
38+
39+
// Iterate and check all expectations
40+
expectedInts := []int{
41+
100, 50, 150,
42+
}
43+
expectedStrings := []string{
44+
"100", "50", "150",
45+
}
46+
47+
i := 0
48+
for k, v := range m.All() {
49+
compare(t, k, expectedInts[i])
50+
compare(t, v, expectedStrings[i])
51+
i++
52+
}
53+
54+
// Deleting 20 changes nothing
55+
m.Delete(20)
56+
i = 0
57+
for k, v := range m.All() {
58+
compare(t, k, expectedInts[i])
59+
compare(t, v, expectedStrings[i])
60+
i++
61+
}
62+
63+
// Deleting the first reorders the list
64+
m.Delete(50)
65+
expectedInts = []int{
66+
100, 150,
67+
}
68+
expectedStrings = []string{
69+
"100", "150",
70+
}
71+
i = 0
72+
for k, v := range m.All() {
73+
compare(t, k, expectedInts[i])
74+
compare(t, v, expectedStrings[i])
75+
i++
76+
}
77+
78+
m.Delete(100)
79+
m.Delete(150)
80+
81+
for range m.All() {
82+
check(t, false) // always fails, length should be 0 now
83+
}
84+
}
85+
86+
func TestIndexMap(t *testing.T) {
87+
m := NewIndexMap[int, string]()
88+
89+
// Add and check 100
90+
m.Put(100, "100")
91+
v, ok := m.Get(100)
92+
check(t, ok)
93+
compare(t, v, "100")
94+
95+
96+
// Doesn't have 50
97+
v, ok = m.Get(50)
98+
check(t, !ok)
99+
compare(t, v, "")
100+
101+
102+
// Add and check 50 (inside current bounds)
103+
m.Put(50, "50")
104+
v, ok = m.Get(50)
105+
check(t, ok)
106+
compare(t, v, "50")
107+
108+
// Doesn't have 150
109+
v, ok = m.Get(150)
110+
check(t, !ok)
111+
compare(t, v, "")
112+
113+
33114
// Add and check 150 (outside current bounds)
34115
m.Put(150, "150")
35116
v, ok = m.Get(150)

ds/indexmap.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package ds
2+
3+
import (
4+
"iter"
5+
6+
"golang.org/x/exp/constraints"
7+
)
8+
9+
// Acts like a map, but backed by an integer indexed slice instead of a map
10+
// This is mostly for use cases where you have a small list of increasing numbers that you want to store in an array, but you dont want to worry about ensuring the bounds are always correct
11+
// Note: If you put a huge key in here, the slice will allocate a ton of space.
12+
type IndexMap[K constraints.Integer, V any] struct {
13+
// TODO: set slice should just be a bitmask
14+
set []bool // Tracks whether or not the data at a location is set or empty
15+
slice []V // Tracks the data
16+
}
17+
18+
func NewIndexMap[K constraints.Integer, V any]() IndexMap[K, V] {
19+
return IndexMap[K, V]{
20+
set: make([]bool, 0),
21+
slice: make([]V, 0),
22+
}
23+
}
24+
25+
func(m *IndexMap[K, V]) grow(idx K) {
26+
requiredLength := idx + 1
27+
growAmount := requiredLength - K(len(m.set))
28+
if growAmount <= 0 {
29+
return // No need to grow if the sliceIdx is already in bounds
30+
}
31+
32+
m.set = append(m.set, make([]bool, growAmount)...)
33+
m.slice = append(m.slice, make([]V, growAmount)...)
34+
}
35+
36+
func(m *IndexMap[K, V]) Put(idx K, val V) {
37+
if idx < 0 {
38+
return
39+
}
40+
41+
m.grow(idx) // Ensure index is within bounds
42+
43+
m.set[idx] = true
44+
m.slice[idx] = val
45+
}
46+
47+
func(m *IndexMap[K, V]) Get(idx K) (V, bool) {
48+
if idx < 0 || idx >= K(len(m.set)) {
49+
var v V
50+
return v, false
51+
}
52+
53+
return m.slice[idx], m.set[idx]
54+
}
55+
56+
// Delete a specific index
57+
func(m *IndexMap[K, V]) Delete(idx K) {
58+
m.set[idx] = false
59+
}
60+
61+
// Clear the entire slice
62+
func(m *IndexMap[K, V]) Clear() {
63+
m.slice = m.slice[:0]
64+
}
65+
66+
// Iterate through the entire slice
67+
func(m *IndexMap[K, V]) All() iter.Seq2[K, V] {
68+
return func(yield func(K, V) bool) {
69+
for i, v := range m.slice {
70+
// Ensure that the map key is set
71+
if !m.set[i] {
72+
continue
73+
}
74+
75+
if !yield(K(i), v) {
76+
break
77+
}
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)