From 73d26bf458d26af9d78162b401b892e6076943b7 Mon Sep 17 00:00:00 2001 From: Paul Taykalo Date: Fri, 25 Oct 2019 22:29:27 +0300 Subject: [PATCH] Use firsIndexAssumingSorted when searching in sorted arrays --- .../RandomAccessCollection+Swiftlint.swift | 57 +++++++++++++++++++ .../Extensions/SyntaxMap+SwiftLint.swift | 46 ++------------- 2 files changed, 62 insertions(+), 41 deletions(-) create mode 100644 Source/SwiftLintFramework/Extensions/RandomAccessCollection+Swiftlint.swift diff --git a/Source/SwiftLintFramework/Extensions/RandomAccessCollection+Swiftlint.swift b/Source/SwiftLintFramework/Extensions/RandomAccessCollection+Swiftlint.swift new file mode 100644 index 0000000000..3b97b7c803 --- /dev/null +++ b/Source/SwiftLintFramework/Extensions/RandomAccessCollection+Swiftlint.swift @@ -0,0 +1,57 @@ +internal extension RandomAccessCollection where Index == Int { + /// Returns the first index in which an element of the collection satisfies + /// the given predicate. The collection assumed to be sorted. If collection is not have sorted values + /// the result is undefined + /// + /// The idea is to get first index of a function where predicate starting to return true values. + /// + /// let values = [1,2,3,4,5] + /// let idx = values.firstIndexAssumingSorted(where: { $0 > 3 }) + /// + /// // false, false, false, true, true + /// // ^ + /// // therefore idx == 3 + /// + /// - Parameter predicate: A closure that takes an element as its argument + /// and returns a Boolean value that indicates whether the passed element + /// represents a match. + /// - Returns: The index of the first element for which `predicate` returns + /// `true`. If no elements in the collection satisfy the given predicate, + /// returns `nil`. + /// + /// - Complexity: O(log(*n*)), where *n* is the length of the collection. + @inlinable + func firstIndexAssumingSorted(where predicate: (Self.Element) throws -> Bool) rethrows -> Int? { + // Predicate should divide a collection to two pars of vaues + // "bad" values for which predicate returns `false`` + // "good" values for which predicate return `true` + + // false false false false false true true true + // ^ + // The idea is to get _first_ index which for which predicate returns `true` + + let lastIndex = count + + // The index that represetns where bad values starts. + var badIndex = -1 + + // The index that represetns where good values starts + var goodIndex = lastIndex + var midIndex = (badIndex + goodIndex) / 2 + + while badIndex + 1 < goodIndex { + if try predicate(self[midIndex]) { + goodIndex = midIndex + } else { + badIndex = midIndex + } + midIndex = (badIndex + goodIndex) / 2 + } + + // Corner case, we' re out of bound, no good items in array + if midIndex == lastIndex { + return nil + } + return goodIndex + } +} diff --git a/Source/SwiftLintFramework/Extensions/SyntaxMap+SwiftLint.swift b/Source/SwiftLintFramework/Extensions/SyntaxMap+SwiftLint.swift index 345773199b..4f39409443 100644 --- a/Source/SwiftLintFramework/Extensions/SyntaxMap+SwiftLint.swift +++ b/Source/SwiftLintFramework/Extensions/SyntaxMap+SwiftLint.swift @@ -11,59 +11,23 @@ extension SyntaxMap { .intersects(byteRange) } - guard let startIndex = firstIntersectingTokenIndex(inByteRange: byteRange) else { - return [] + func intersectsOrAfter(_ token: SyntaxToken) -> Bool { + return token.offset + token.length > byteRange.location } - func isAfterByteRange(_ token: SyntaxToken) -> Bool { - return token.offset < byteRange.upperBound + guard let startIndex = tokens.firstIndexAssumingSorted(where: intersectsOrAfter) else { + return [] } let tokensAfterFirstIntersection = tokens .lazy .suffix(from: startIndex) - .prefix(while: isAfterByteRange) + .prefix(while: { $0.offset < byteRange.upperBound }) .filter(intersect) return Array(tokensAfterFirstIntersection) } - // Index of first token which intersects byterange - // Using binary search - func firstIntersectingTokenIndex(inByteRange byteRange: NSRange) -> Int? { - let lastIndex = tokens.count - - // The idx, which definitely beyound byteOffset - var bad = -1 - - // The index which is definitely after byteOffset - var good = lastIndex - var mid = (bad + good) / 2 - - // 0 0 0 0 0 1 1 1 1 1 - // ^ - // The idea is to get _first_ token index which intesects the byteRange - func intersectsOrAfter(at index: Int) -> Bool { - let token = tokens[index] - return token.offset + token.length > byteRange.location - } - - while bad + 1 < good { - if intersectsOrAfter(at: mid) { - good = mid - } else { - bad = mid - } - mid = (bad + good) / 2 - } - - // Corner case, we' re out of bound, no good items in array - if mid == lastIndex { - return nil - } - return good - } - internal func kinds(inByteRange byteRange: NSRange) -> [SyntaxKind] { return tokens(inByteRange: byteRange).kinds }