diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 992ff8a..d7ed806 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,15 +26,32 @@ jobs: uses: actions/cache@v1 with: path: .build - key: build-${{ github.workspace }}-${{ runner.os }}-${{ matrix.swift-compat-ver }}-${{ hashFiles('Package.*') }} + key: build-${{ github.workspace }}-${{ runner.os }}-${{ matrix.swift-compat-ver }}-${{ hashFiles('Package.*') }}-${{ hashFiles('Sources/**/*.swift') }} restore-keys: | + build-${{ github.workspace }}-${{ runner.os }}-${{ matrix.swift-compat-ver }}-${{ hashFiles('Package.*') }}- build-${{ github.workspace }}-${{ runner.os }}-${{ matrix.swift-compat-ver }}- build-${{ github.workspace }}-${{ runner.os }}- build-${{ github.workspace }}- - uses: YOCKOW/Action-setup-swift@master with: - swift-version: '5.1.4' + swift-version: '5.2' + - name: Try to build products with debug mode. + run: | + swift build --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + if [ $? != 0 ]; then + echo "Failed to build products with debug mode." + rm -rf $(cd .build/debug && pwd -P) + fi + continue-on-error: true - name: Test with debug mode. run: swift test --configuration debug -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + - name: Try to build products with release mode. + run: | + swift build --configuration release -Xswiftc -enable-testing -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} + if [ $? != 0 ]; then + echo "Failed to build products with release mode." + rm -rf $(cd .build/release && pwd -P) + fi + continue-on-error: true - name: Test with release mode. run: swift test --configuration release -Xswiftc -enable-testing -Xswiftc -swift-version -Xswiftc ${{ matrix.swift-compat-ver }} diff --git a/Sources/Ranges/Memoizables.swift b/Sources/Ranges/Memoizables.swift index 13d91ff..f90ff71 100644 --- a/Sources/Ranges/Memoizables.swift +++ b/Sources/Ranges/Memoizables.swift @@ -4,48 +4,54 @@ Licensed under MIT License. See "LICENSE.txt" for more information. ************************************************************************************************ */ - -/// Immutable multiple ranges. -/// Results can be memoized. -public final class MemoizableMultipleRanges where Bound: Comparable & Hashable { - private var _memoized: [Bound: Bool] = [:] - private var _multipleRanges: MultipleRanges - - public init(_ ranges: MultipleRanges) { - self._multipleRanges = ranges - } - - public func contains(_ value: Bound) -> Bool { - func _contains(_ value: Bound) -> Bool { - let result = self._multipleRanges.contains(value) - self._memoized[value] = result - return result - } - return self._memoized[value] ?? _contains(value) - } -} - /// Immutable range-dictionary. /// Results can be memoized. public final class MemoizableRangeDictionary where Bound: Comparable & Hashable { private var _memoized: [Bound: Value?] = [:] + private var _recentPairs: ArraySlice._Pair> = [] private var _rangeDictionary: RangeDictionary public init(_ rangeDictionary: RangeDictionary) { self._rangeDictionary = rangeDictionary } - public subscript(_ element: Bound) -> Value? { - func _value() -> Value? { - let result = self._rangeDictionary[element] - self._memoized[element] = result - return result + private func _memoizedValue(for bound: Bound) -> Value?? { + return self._memoized[bound] + } + + private func _recentValue(for bound: Bound) -> Value? { + for pair in self._recentPairs { + if pair.range.contains(bound) { return pair.value } } - + return nil + } + + private func _appendRecentPair(_ pair: RangeDictionary._Pair) { + self._recentPairs.append(pair) + self._recentPairs = self._recentPairs.suffix(3) + } + + private func _valueWithMemoizing(for bound: Bound) -> Value? { + if let index = self._rangeDictionary._index(whereRangeContains: bound) { + let pair = self._rangeDictionary._rangesAndValues[index] + self._memoized[bound] = pair.value + self._appendRecentPair(pair) + return pair.value + } else { + self._memoized[bound] = Optional.none + return nil + } + } + + public subscript(_ element: Bound) -> Value? { switch self._memoized[element] { case Optional.none: - return _value() + if let value = self._recentValue(for: element) { + self._memoized[element] = value + return value + } + return _valueWithMemoizing(for: element) case Optional.some(.none): return nil case Optional.some(.some(let value)): @@ -53,3 +59,21 @@ public final class MemoizableRangeDictionary where Bound: Comparab } } } + + + +/// Immutable multiple ranges. +/// Results can be memoized. +public final class MemoizableMultipleRanges where Bound: Comparable & Hashable { + private var _memoized: MemoizableRangeDictionary + + public init(_ ranges: MultipleRanges) { + self._memoized = .init(ranges._rangeDictionary) + } + + public func contains(_ value: Bound) -> Bool { + return self._memoized[value] != nil + } +} + + diff --git a/Sources/Ranges/MultipleRanges.swift b/Sources/Ranges/MultipleRanges.swift index 1a2cfb2..32e7fd3 100644 --- a/Sources/Ranges/MultipleRanges.swift +++ b/Sources/Ranges/MultipleRanges.swift @@ -17,7 +17,7 @@ private func _bitCastArrayNoneToVoid(_ array: Array>) -> Array<(A /// Represents multiple ranges. public struct MultipleRanges where Bound: Comparable { - private var _rangeDictionary: RangeDictionary + internal var _rangeDictionary: RangeDictionary private init(_ rangeDictionary: RangeDictionary) { self._rangeDictionary = rangeDictionary diff --git a/Sources/Ranges/RangeDictionary.swift b/Sources/Ranges/RangeDictionary.swift index bbf3164..d0d6105 100644 --- a/Sources/Ranges/RangeDictionary.swift +++ b/Sources/Ranges/RangeDictionary.swift @@ -28,10 +28,10 @@ */ public struct RangeDictionary where Bound: Comparable { - fileprivate typealias _Pair = (range: AnyRange, value: Value) + internal typealias _Pair = (range: AnyRange, value: Value) /// Must be always sorted with the ranges. - fileprivate private(set) var _rangesAndValues: [_Pair] + internal private(set) var _rangesAndValues: [_Pair] private func _twoRanges(from index: Int) -> (AnyRange, AnyRange) { let (pair0, pair1) = self._rangesAndValues._twoElements(from: index) return (pair0.range, pair1.range) @@ -82,7 +82,7 @@ public struct RangeDictionary where Bound: Comparable { } } - private func _index(whereRangeContains element: Bound) -> Int? { + internal func _index(whereRangeContains element: Bound) -> Int? { func _binarySearch(_ collection: C, _ element: Bound) -> Int? where C: Collection, C.Index == Int, C.Element == _Pair { diff --git a/Tests/RangesTests/MemoizablesTests.swift b/Tests/RangesTests/MemoizablesTests.swift index 7189644..af09f2c 100644 --- a/Tests/RangesTests/MemoizablesTests.swift +++ b/Tests/RangesTests/MemoizablesTests.swift @@ -16,6 +16,7 @@ final class MemoizablesTests: XCTestCase { XCTAssertTrue(ranges.contains(555)) XCTAssertFalse(ranges.contains(5555)) XCTAssertTrue(ranges.contains(5)) // again + XCTAssertTrue(ranges.contains(7)) // near } func test_dictionary() { @@ -30,5 +31,6 @@ final class MemoizablesTests: XCTestCase { XCTAssertEqual(dictionary[25], "20") XCTAssertEqual(dictionary[10000], nil) XCTAssertEqual(dictionary[5], "0") // again + XCTAssertEqual(dictionary[7], "0") // near } }