//===----------------------------------------------------------------------===// // // This source file is part of the AsyncHTTPClient open source project // // Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information // See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors // // SPDX-License-Identifier: Apache-2.0 // //===----------------------------------------------------------------------===// struct LRUCache { private typealias Generation = UInt64 private struct Element { var generation: Generation var key: Key var value: Value } private let capacity: Int private var generation: Generation = 0 private var elements: [Element] init(capacity: Int = 8) { precondition(capacity > 0, "capacity needs to be > 0") self.capacity = capacity self.elements = [] self.elements.reserveCapacity(capacity) } private mutating func bumpGenerationAndFindIndex(key: Key) -> Int? { self.generation += 1 let found = self.elements.firstIndex { element in element.key == key } return found } mutating func find(key: Key) -> Value? { if let found = self.bumpGenerationAndFindIndex(key: key) { self.elements[found].generation = self.generation return self.elements[found].value } else { return nil } } @discardableResult mutating func append(key: Key, value: Value) -> Value { let newElement = Element( generation: self.generation, key: key, value: value ) if let found = self.bumpGenerationAndFindIndex(key: key) { self.elements[found] = newElement return value } if self.elements.count < self.capacity { self.elements.append(newElement) return value } assert(self.elements.count == self.capacity) assert(self.elements.count > 0) let minIndex = self.elements.minIndex { l, r in l.generation < r.generation }! self.elements.swapAt(minIndex, self.elements.endIndex - 1) self.elements.removeLast() self.elements.append(newElement) return value } mutating func findOrAppend(key: Key, _ valueGenerator: (Key) -> Value) -> Value { if let found = self.find(key: key) { return found } return self.append(key: key, value: valueGenerator(key)) } } extension Array { func minIndex(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Index? { guard var minSoFar: (Index, Element) = self.first.map({ (0, $0) }) else { return nil } for indexElement in self.enumerated() { if try areInIncreasingOrder(indexElement.1, minSoFar.1) { minSoFar = indexElement } } return minSoFar.0 } }