Search Apps Documentation Source Content File Folder Download Copy Actions Download

lru.gno

3.84 Kb · 169 lines
  1package lru
  2
  3import (
  4	"strconv"
  5	"strings"
  6
  7	"chain"
  8)
  9
 10// capacity is the fixed maximum number of entries the cache holds.
 11const capacity = 8
 12
 13// node is a doubly-linked-list node holding a key/value pair.
 14// The list is ordered MRU (head) -> LRU (tail).
 15type node struct {
 16	key   string
 17	value string
 18	prev  *node
 19	next  *node
 20}
 21
 22// cache is a fixed-capacity LRU cache backed by a doubly-linked list and
 23// an index from key -> node for O(1) lookups.
 24type cache struct {
 25	index map[string]*node
 26	head  *node // most-recently-used
 27	tail  *node // least-recently-used
 28	size  int
 29	hits  int
 30	miss  int
 31}
 32
 33var c = &cache{index: make(map[string]*node)}
 34
 35// detach removes n from the linked list (does not touch the index).
 36func (ca *cache) detach(n *node) {
 37	if n.prev != nil {
 38		n.prev.next = n.next
 39	} else {
 40		ca.head = n.next
 41	}
 42	if n.next != nil {
 43		n.next.prev = n.prev
 44	} else {
 45		ca.tail = n.prev
 46	}
 47	n.prev = nil
 48	n.next = nil
 49}
 50
 51// pushFront inserts n at the head (MRU position).
 52func (ca *cache) pushFront(n *node) {
 53	n.prev = nil
 54	n.next = ca.head
 55	if ca.head != nil {
 56		ca.head.prev = n
 57	}
 58	ca.head = n
 59	if ca.tail == nil {
 60		ca.tail = n
 61	}
 62}
 63
 64// touch moves an existing node to the MRU position.
 65func (ca *cache) touch(n *node) {
 66	ca.detach(n)
 67	ca.pushFront(n)
 68}
 69
 70// evict removes the LRU entry (tail) and returns its key.
 71func (ca *cache) evict() string {
 72	n := ca.tail
 73	if n == nil {
 74		return ""
 75	}
 76	ca.detach(n)
 77	delete(ca.index, n.key)
 78	ca.size--
 79	return n.key
 80}
 81
 82// Put inserts or updates key with value and marks it most-recently-used.
 83// Evicts the least-recently-used entry if capacity is exceeded.
 84func Put(cur realm, key string, value string) {
 85	if n, ok := c.index[key]; ok {
 86		n.value = value
 87		c.touch(n)
 88		chain.Emit("Put", "key", key, "op", "update")
 89		return
 90	}
 91	n := &node{key: key, value: value}
 92	c.index[key] = n
 93	c.pushFront(n)
 94	c.size++
 95	evicted := ""
 96	if c.size > capacity {
 97		evicted = c.evict()
 98	}
 99	if evicted != "" {
100		chain.Emit("Put", "key", key, "op", "insert", "evicted", evicted)
101	} else {
102		chain.Emit("Put", "key", key, "op", "insert")
103	}
104}
105
106// Get returns the value for key and marks it most-recently-used.
107// It records a hit or a miss. The returned bool reports whether the key
108// was present.
109func Get(cur realm, key string) (string, bool) {
110	if n, ok := c.index[key]; ok {
111		c.touch(n)
112		c.hits++
113		chain.Emit("Get", "key", key, "result", "hit")
114		return n.value, true
115	}
116	c.miss++
117	chain.Emit("Get", "key", key, "result", "miss")
118	return "", false
119}
120
121// Reset clears the cache and stats.
122func Reset(cur realm) {
123	c = &cache{index: make(map[string]*node)}
124	chain.Emit("Reset")
125}
126
127// peek returns the value without affecting recency or stats (read helper).
128func peek(key string) (string, bool) {
129	if n, ok := c.index[key]; ok {
130		return n.value, true
131	}
132	return "", false
133}
134
135// Render displays the cache contents in MRU->LRU order with stats.
136func Render(path string) string {
137	var b strings.Builder
138	b.WriteString("# LRU Cache\n\n")
139	b.WriteString("A fixed-capacity least-recently-used cache.\n\n")
140
141	b.WriteString("## Stats\n\n")
142	b.WriteString("- Capacity: " + strconv.Itoa(capacity) + "\n")
143	b.WriteString("- Size: " + strconv.Itoa(c.size) + "\n")
144	b.WriteString("- Hits: " + strconv.Itoa(c.hits) + "\n")
145	b.WriteString("- Misses: " + strconv.Itoa(c.miss) + "\n")
146	total := c.hits + c.miss
147	if total > 0 {
148		// integer-percent hit rate, deterministic
149		rate := (c.hits * 100) / total
150		b.WriteString("- Hit rate: " + strconv.Itoa(rate) + "%\n")
151	} else {
152		b.WriteString("- Hit rate: n/a\n")
153	}
154	b.WriteString("\n")
155
156	b.WriteString("## Entries (MRU → LRU)\n\n")
157	if c.head == nil {
158		b.WriteString("_empty_\n")
159		return b.String()
160	}
161	b.WriteString("| # | Key | Value |\n")
162	b.WriteString("|---|-----|-------|\n")
163	i := 1
164	for n := c.head; n != nil; n = n.next {
165		b.WriteString("| " + strconv.Itoa(i) + " | " + n.key + " | " + n.value + " |\n")
166		i++
167	}
168	return b.String()
169}