Skip to content

Commit e85c798

Browse files
committed
Add arraymap data structure
1 parent 9fca307 commit e85c798

File tree

4 files changed

+159
-26
lines changed

4 files changed

+159
-26
lines changed

ds/arraymap.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 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
16+
}
17+
18+
func NewArrayMap[K constraints.Integer, V any]() ArrayMap[K, V] {
19+
return ArrayMap[K, V]{
20+
set: make([]bool, 0),
21+
slice: make([]V, 0),
22+
}
23+
}
24+
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
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 *ArrayMap[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 *ArrayMap[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 *ArrayMap[K, V]) Delete(idx K) {
58+
m.set[idx] = false
59+
}
60+
61+
// Clear the entire slice
62+
func(m *ArrayMap[K, V]) Clear() {
63+
m.slice = m.slice[:0]
64+
}
65+
66+
// Iterate through the entire slice
67+
func(m *ArrayMap[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+
}

ds/arraymap_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package ds
2+
3+
import "testing"
4+
5+
func TestArrayMap(t *testing.T) {
6+
m := NewArrayMap[int, string]()
7+
8+
// Add and check 100
9+
m.Put(100, "100")
10+
v, ok := m.Get(100)
11+
check(t, ok)
12+
compare(t, v, "100")
13+
14+
15+
// Doesn't have 50
16+
v, ok = m.Get(50)
17+
check(t, !ok)
18+
compare(t, v, "")
19+
20+
21+
// Add and check 50 (inside current bounds)
22+
m.Put(50, "50")
23+
v, ok = m.Get(50)
24+
check(t, ok)
25+
compare(t, v, "50")
26+
27+
// Doesn't have 150
28+
v, ok = m.Get(150)
29+
check(t, !ok)
30+
compare(t, v, "")
31+
32+
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+
50, 100, 150,
42+
}
43+
expectedStrings := []string{
44+
"50", "100", "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 makes us skip it in the expected list
64+
m.Delete(50)
65+
i = 1
66+
for k, v := range m.All() {
67+
compare(t, k, expectedInts[i])
68+
compare(t, v, expectedStrings[i])
69+
i++
70+
}
71+
72+
}

ds/slicemap.go

Lines changed: 0 additions & 26 deletions
This file was deleted.

glm/rect.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,13 @@ func (r Rect) BottomHalf() Rect {
251251
return r.CutBottom(0.5 * r.H())
252252
}
253253

254+
// Returns a centered square with height and width set by amount
255+
func (r Rect) SliceSquare(amount float64) Rect {
256+
r = r.SliceHorizontal(amount)
257+
r = r.SliceVertical(amount)
258+
return r
259+
}
260+
254261
// Returns a centered horizontal sliver with height set by amount
255262
func (r Rect) SliceHorizontal(amount float64) Rect {
256263
r.CutTop((r.H() - amount) / 2)

0 commit comments

Comments
 (0)