memoization.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. // Package memoization implement a simple memoization cache. It's designed to
  2. // improve performance in textarea.
  3. package textarea
  4. import (
  5. "container/list"
  6. "crypto/sha256"
  7. "fmt"
  8. "sync"
  9. )
  10. // Hasher is an interface that requires a Hash method. The Hash method is
  11. // expected to return a string representation of the hash of the object.
  12. type Hasher interface {
  13. Hash() string
  14. }
  15. // entry is a struct that holds a key-value pair. It is used as an element
  16. // in the evictionList of the MemoCache.
  17. type entry[T any] struct {
  18. key string
  19. value T
  20. }
  21. // MemoCache is a struct that represents a cache with a set capacity. It
  22. // uses an LRU (Least Recently Used) eviction policy. It is safe for
  23. // concurrent use.
  24. type MemoCache[H Hasher, T any] struct {
  25. capacity int
  26. mutex sync.Mutex
  27. cache map[string]*list.Element // The cache holding the results
  28. evictionList *list.List // A list to keep track of the order for LRU
  29. hashableItems map[string]T // This map keeps track of the original hashable items (optional)
  30. }
  31. // NewMemoCache is a function that creates a new MemoCache with a given
  32. // capacity. It returns a pointer to the created MemoCache.
  33. func NewMemoCache[H Hasher, T any](capacity int) *MemoCache[H, T] {
  34. return &MemoCache[H, T]{
  35. capacity: capacity,
  36. cache: make(map[string]*list.Element),
  37. evictionList: list.New(),
  38. hashableItems: make(map[string]T),
  39. }
  40. }
  41. // Capacity is a method that returns the capacity of the MemoCache.
  42. func (m *MemoCache[H, T]) Capacity() int {
  43. return m.capacity
  44. }
  45. // Size is a method that returns the current size of the MemoCache. It is
  46. // the number of items currently stored in the cache.
  47. func (m *MemoCache[H, T]) Size() int {
  48. m.mutex.Lock()
  49. defer m.mutex.Unlock()
  50. return m.evictionList.Len()
  51. }
  52. // Get is a method that returns the value associated with the given
  53. // hashable item in the MemoCache. If there is no corresponding value, the
  54. // method returns nil.
  55. func (m *MemoCache[H, T]) Get(h H) (T, bool) {
  56. m.mutex.Lock()
  57. defer m.mutex.Unlock()
  58. hashedKey := h.Hash()
  59. if element, found := m.cache[hashedKey]; found {
  60. m.evictionList.MoveToFront(element)
  61. return element.Value.(*entry[T]).value, true
  62. }
  63. var result T
  64. return result, false
  65. }
  66. // Set is a method that sets the value for the given hashable item in the
  67. // MemoCache. If the cache is at capacity, it evicts the least recently
  68. // used item before adding the new item.
  69. func (m *MemoCache[H, T]) Set(h H, value T) {
  70. m.mutex.Lock()
  71. defer m.mutex.Unlock()
  72. hashedKey := h.Hash()
  73. if element, found := m.cache[hashedKey]; found {
  74. m.evictionList.MoveToFront(element)
  75. element.Value.(*entry[T]).value = value
  76. return
  77. }
  78. // Check if the cache is at capacity
  79. if m.evictionList.Len() >= m.capacity {
  80. // Evict the least recently used item from the cache
  81. toEvict := m.evictionList.Back()
  82. if toEvict != nil {
  83. evictedEntry := m.evictionList.Remove(toEvict).(*entry[T])
  84. delete(m.cache, evictedEntry.key)
  85. delete(m.hashableItems, evictedEntry.key) // if you're keeping track of original items
  86. }
  87. }
  88. // Add the value to the cache and the evictionList
  89. newEntry := &entry[T]{
  90. key: hashedKey,
  91. value: value,
  92. }
  93. element := m.evictionList.PushFront(newEntry)
  94. m.cache[hashedKey] = element
  95. m.hashableItems[hashedKey] = value // if you're keeping track of original items
  96. }
  97. // HString is a type that implements the Hasher interface for strings.
  98. type HString string
  99. // Hash is a method that returns the hash of the string.
  100. func (h HString) Hash() string {
  101. return fmt.Sprintf("%x", sha256.Sum256([]byte(h)))
  102. }
  103. // HInt is a type that implements the Hasher interface for integers.
  104. type HInt int
  105. // Hash is a method that returns the hash of the integer.
  106. func (h HInt) Hash() string {
  107. return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d", h))))
  108. }