|
1 |
| -import Foundation |
2 |
| -import OrderedCollections |
| 1 | +#if DEBUG |
| 2 | + import Foundation |
| 3 | + import OrderedCollections |
3 | 4 |
|
4 |
| -final class MemoizedCache<Key: Hashable, Value>: @unchecked Sendable { |
5 |
| - private var dictionary = OrderedDictionary<Key, Value>() |
6 |
| - private var lock = NSLock() |
7 |
| - private let maxCapacity: Int |
8 |
| - |
9 |
| - // TODO possibly remove these stats later |
10 |
| - private var totalEvictions = 0 |
11 |
| - private var totalCalls = 0 |
12 |
| - private var totalHits = 0 |
13 |
| - |
14 |
| - init(maxCapacity: Int = 500) { |
15 |
| - self.maxCapacity = maxCapacity |
16 |
| - } |
17 |
| - |
18 |
| - func printStats() { |
19 |
| - lock.sync { |
20 |
| - let hitRate = (Float(totalHits) / Float(totalCalls)) * 100 |
21 |
| - let evictionRate = (Float(totalEvictions) / Float(totalCalls)) * 100 |
22 |
| - print("size = \(dictionary.count), totalCalls = \(totalCalls), hitRate = \(hitRate)%, evictionRate = \(evictionRate)%") |
| 5 | + func memoize<Result>( |
| 6 | + maxCapacity: Int = 500, |
| 7 | + _ apply: @escaping () -> Result |
| 8 | + ) -> () -> Result { |
| 9 | + let cache = Cache<[NSNumber], Result>(maxCapacity: maxCapacity) |
| 10 | + return { |
| 11 | + let callStack = Thread.callStackReturnAddresses |
| 12 | + guard let memoizedResult = cache[callStack] |
| 13 | + else { |
| 14 | + let result = apply() |
| 15 | + defer { cache[callStack] = result } |
| 16 | + return result |
| 17 | + } |
| 18 | + return memoizedResult |
23 | 19 | }
|
24 | 20 | }
|
25 |
| - |
26 |
| - subscript(key: Key) -> Value? { |
27 |
| - get { |
28 |
| - lock.sync { |
29 |
| - totalCalls += 1 |
30 |
| - if let value = dictionary[key] { |
31 |
| - totalHits += 1 |
32 |
| - return value |
| 21 | + |
| 22 | + private final class Cache<Key: Hashable, Value>: @unchecked Sendable { |
| 23 | + var dictionary = OrderedDictionary<Key, Value>() |
| 24 | + var lock = NSLock() |
| 25 | + let maxCapacity: Int |
| 26 | + init(maxCapacity: Int = 500) { |
| 27 | + self.maxCapacity = maxCapacity |
| 28 | + } |
| 29 | + subscript(key: Key) -> Value? { |
| 30 | + get { |
| 31 | + self.lock.sync { |
| 32 | + self.dictionary[key] |
33 | 33 | }
|
34 |
| - return nil |
35 | 34 | }
|
36 |
| - } |
37 |
| - set { |
38 |
| - lock.sync { |
39 |
| - dictionary[key] = newValue |
40 |
| - if dictionary.count > maxCapacity { |
41 |
| - // evict first (oldest) element |
42 |
| - dictionary.removeFirst() |
43 |
| - totalEvictions += 1 |
| 35 | + set { |
| 36 | + self.lock.sync { |
| 37 | + self.dictionary[key] = newValue |
| 38 | + if self.dictionary.count > self.maxCapacity { |
| 39 | + self.dictionary.removeFirst() |
| 40 | + } |
44 | 41 | }
|
45 | 42 | }
|
46 | 43 | }
|
47 | 44 | }
|
48 |
| - |
49 |
| -} |
50 |
| - |
51 |
| -func memoize<Input: Hashable, Result>( |
52 |
| - maxCapacity: Int = 500, |
53 |
| - _ apply: @escaping (Input) -> Result |
54 |
| -) -> (Input) -> Result { |
55 |
| - let cache = MemoizedCache<Input, Result>(maxCapacity: maxCapacity) |
56 |
| - |
57 |
| - return { input in |
58 |
| -// defer { cache.printStats() } |
59 |
| - if let memoizedResult = cache[input] { |
60 |
| - return memoizedResult |
61 |
| - } |
62 |
| - |
63 |
| - let result = apply(input) |
64 |
| - cache[input] = result |
65 |
| - return result |
66 |
| - } |
67 |
| - |
68 |
| -} |
| 45 | +#endif |
0 commit comments