mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
The _StringProcessing module provides a generic, collection-based `contains` method that performs poorly for ranges and closed ranges. This addresses the primary issue by providing concrete overloads for Range and ClosedRange which match the expected performance for these operations. This change also fixes an issue with the existing range overlap tests. The generated `(Closed)Range.overlap` tests are ignoring the "other" range type when generating ranges for testing, so all overlap tests are only being run against ranges of the same type. This fixes things so that heterogeneous testing is included.
599 lines
20 KiB
Swift
599 lines
20 KiB
Swift
//===--- ClosedRange.swift ------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// FIXME: swift-3-indexing-model: Generalize all tests to check both
|
|
// [Closed]Range.
|
|
|
|
/// An interval from a lower bound up to, and including, an upper bound.
|
|
///
|
|
/// You create a `ClosedRange` instance by using the closed range
|
|
/// operator (`...`).
|
|
///
|
|
/// let throughFive = 0...5
|
|
///
|
|
/// A `ClosedRange` instance contains both its lower bound and its
|
|
/// upper bound.
|
|
///
|
|
/// throughFive.contains(3)
|
|
/// // true
|
|
/// throughFive.contains(10)
|
|
/// // false
|
|
/// throughFive.contains(5)
|
|
/// // true
|
|
///
|
|
/// Because a closed range includes its upper bound, a closed range whose lower
|
|
/// bound is equal to the upper bound contains that value. Therefore, a
|
|
/// `ClosedRange` instance cannot represent an empty range.
|
|
///
|
|
/// let zeroInclusive = 0...0
|
|
/// zeroInclusive.contains(0)
|
|
/// // true
|
|
/// zeroInclusive.isEmpty
|
|
/// // false
|
|
///
|
|
/// Using a Closed Range as a Collection of Consecutive Values
|
|
/// ----------------------------------------------------------
|
|
///
|
|
/// When a closed range uses integers as its lower and upper bounds, or any
|
|
/// other type that conforms to the `Strideable` protocol with an integer
|
|
/// stride, you can use that range in a `for`-`in` loop or with any sequence or
|
|
/// collection method. The elements of the range are the consecutive values
|
|
/// from its lower bound up to, and including, its upper bound.
|
|
///
|
|
/// for n in 3...5 {
|
|
/// print(n)
|
|
/// }
|
|
/// // Prints "3"
|
|
/// // Prints "4"
|
|
/// // Prints "5"
|
|
///
|
|
/// Because floating-point types such as `Float` and `Double` are their own
|
|
/// `Stride` types, they cannot be used as the bounds of a countable range. If
|
|
/// you need to iterate over consecutive floating-point values, see the
|
|
/// `stride(from:through:by:)` function.
|
|
@frozen
|
|
public struct ClosedRange<Bound: Comparable> {
|
|
/// The range's lower bound.
|
|
public let lowerBound: Bound
|
|
|
|
/// The range's upper bound.
|
|
public let upperBound: Bound
|
|
|
|
// This works around _debugPrecondition() impacting the performance of
|
|
// optimized code. (rdar://72246338)
|
|
@_alwaysEmitIntoClient @inline(__always)
|
|
internal init(_uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
|
|
self.lowerBound = bounds.lower
|
|
self.upperBound = bounds.upper
|
|
}
|
|
|
|
/// Creates an instance with the given bounds.
|
|
///
|
|
/// Because this initializer does not perform any checks, it should be used
|
|
/// as an optimization only when you are absolutely certain that `lower` is
|
|
/// less than or equal to `upper`. Using the closed range operator (`...`)
|
|
/// to form `ClosedRange` instances is preferred.
|
|
///
|
|
/// - Parameter bounds: A tuple of the lower and upper bounds of the range.
|
|
@inlinable
|
|
@unsafe
|
|
public init(uncheckedBounds bounds: (lower: Bound, upper: Bound)) {
|
|
_debugPrecondition(bounds.lower <= bounds.upper,
|
|
"ClosedRange requires lowerBound <= upperBound")
|
|
self.init(_uncheckedBounds: (lower: bounds.lower, upper: bounds.upper))
|
|
}
|
|
}
|
|
|
|
// define isEmpty, which is available even on an uncountable ClosedRange
|
|
extension ClosedRange {
|
|
/// A Boolean value indicating whether the range contains no elements.
|
|
///
|
|
/// Because a closed range cannot represent an empty range, this property is
|
|
/// always `false`.
|
|
@inlinable
|
|
public var isEmpty: Bool {
|
|
return false
|
|
}
|
|
}
|
|
|
|
extension ClosedRange: RangeExpression {
|
|
@inlinable // trivial-implementation
|
|
public func relative<C: Collection>(to collection: C) -> Range<Bound>
|
|
where C.Index == Bound {
|
|
return Range(
|
|
_uncheckedBounds: (
|
|
lower: lowerBound,
|
|
upper: collection.index(after: self.upperBound)))
|
|
}
|
|
|
|
/// Returns a Boolean value indicating whether the given element is contained
|
|
/// within the range.
|
|
///
|
|
/// A `ClosedRange` instance contains both its lower and upper bound.
|
|
/// `element` is contained in the range if it is between the two bounds or
|
|
/// equal to either bound.
|
|
///
|
|
/// - Parameter element: The element to check for containment.
|
|
/// - Returns: `true` if `element` is contained in the range; otherwise,
|
|
/// `false`.
|
|
@inlinable
|
|
public func contains(_ element: Bound) -> Bool {
|
|
return element >= self.lowerBound && element <= self.upperBound
|
|
}
|
|
}
|
|
|
|
extension ClosedRange: Sequence
|
|
where Bound: Strideable, Bound.Stride: SignedInteger {
|
|
public typealias Element = Bound
|
|
public typealias Iterator = IndexingIterator<ClosedRange<Bound>>
|
|
}
|
|
|
|
extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
|
|
@frozen // FIXME(resilience)
|
|
public enum Index {
|
|
case pastEnd
|
|
case inRange(Bound)
|
|
}
|
|
}
|
|
|
|
extension ClosedRange.Index: Comparable {
|
|
@inlinable
|
|
public static func == (
|
|
lhs: ClosedRange<Bound>.Index,
|
|
rhs: ClosedRange<Bound>.Index
|
|
) -> Bool {
|
|
switch (lhs, rhs) {
|
|
case (.inRange(let l), .inRange(let r)):
|
|
return l == r
|
|
case (.pastEnd, .pastEnd):
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public static func < (
|
|
lhs: ClosedRange<Bound>.Index,
|
|
rhs: ClosedRange<Bound>.Index
|
|
) -> Bool {
|
|
switch (lhs, rhs) {
|
|
case (.inRange(let l), .inRange(let r)):
|
|
return l < r
|
|
case (.inRange, .pastEnd):
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ClosedRange.Index: Hashable
|
|
where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable {
|
|
/// Hashes the essential components of this value by feeding them into the
|
|
/// given hasher.
|
|
///
|
|
/// - Parameter hasher: The hasher to use when combining the components
|
|
/// of this instance.
|
|
@inlinable
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
case .inRange(let value):
|
|
hasher.combine(0 as Int8)
|
|
hasher.combine(value)
|
|
case .pastEnd:
|
|
hasher.combine(1 as Int8)
|
|
}
|
|
}
|
|
}
|
|
|
|
// FIXME: this should only be conformance to RandomAccessCollection but
|
|
// the compiler balks without all 3
|
|
extension ClosedRange: Collection, BidirectionalCollection, RandomAccessCollection
|
|
where Bound: Strideable, Bound.Stride: SignedInteger
|
|
{
|
|
// while a ClosedRange can't be empty, a _slice_ of a ClosedRange can,
|
|
// so ClosedRange can't be its own self-slice unlike Range
|
|
public typealias SubSequence = Slice<ClosedRange<Bound>>
|
|
|
|
/// The position of the first element in the range.
|
|
@inlinable
|
|
public var startIndex: Index {
|
|
return .inRange(lowerBound)
|
|
}
|
|
|
|
/// The range's "past the end" position---that is, the position one greater
|
|
/// than the last valid subscript argument.
|
|
@inlinable
|
|
public var endIndex: Index {
|
|
return .pastEnd
|
|
}
|
|
|
|
@inlinable
|
|
public func index(after i: Index) -> Index {
|
|
switch i {
|
|
case .inRange(let x):
|
|
return x == upperBound
|
|
? .pastEnd
|
|
: .inRange(x.advanced(by: 1))
|
|
case .pastEnd:
|
|
_preconditionFailure("Incrementing past end index")
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public func index(before i: Index) -> Index {
|
|
switch i {
|
|
case .inRange(let x):
|
|
_precondition(x > lowerBound, "Incrementing past start index")
|
|
return .inRange(x.advanced(by: -1))
|
|
case .pastEnd:
|
|
_precondition(upperBound >= lowerBound, "Incrementing past start index")
|
|
return .inRange(upperBound)
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public func index(_ i: Index, offsetBy distance: Int) -> Index {
|
|
switch i {
|
|
case .inRange(let x):
|
|
let d = x.distance(to: upperBound)
|
|
if distance <= d {
|
|
let newPosition = x.advanced(by: numericCast(distance))
|
|
_precondition(newPosition >= lowerBound,
|
|
"Advancing past start index")
|
|
return .inRange(newPosition)
|
|
}
|
|
if d - -1 == distance { return .pastEnd }
|
|
_preconditionFailure("Advancing past end index")
|
|
case .pastEnd:
|
|
if distance == 0 {
|
|
return i
|
|
}
|
|
if distance < 0 {
|
|
return index(.inRange(upperBound), offsetBy: numericCast(distance + 1))
|
|
}
|
|
_preconditionFailure("Advancing past end index")
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public func distance(from start: Index, to end: Index) -> Int {
|
|
switch (start, end) {
|
|
case let (.inRange(left), .inRange(right)):
|
|
// in range <--> in range
|
|
return numericCast(left.distance(to: right))
|
|
case let (.inRange(left), .pastEnd):
|
|
// in range --> end
|
|
return numericCast(1 + left.distance(to: upperBound))
|
|
case let (.pastEnd, .inRange(right)):
|
|
// in range <-- end
|
|
return numericCast(upperBound.distance(to: right) - 1)
|
|
case (.pastEnd, .pastEnd):
|
|
// end <--> end
|
|
return 0
|
|
}
|
|
}
|
|
|
|
/// Accesses the element at specified position.
|
|
///
|
|
/// You can subscript a collection with any valid index other than the
|
|
/// collection's end index. The end index refers to the position one past
|
|
/// the last element of a collection, so it doesn't correspond with an
|
|
/// element.
|
|
///
|
|
/// - Parameter position: The position of the element to access. `position`
|
|
/// must be a valid index of the range, and must not equal the range's end
|
|
/// index.
|
|
@inlinable
|
|
public subscript(position: Index) -> Bound {
|
|
// FIXME: swift-3-indexing-model: range checks and tests.
|
|
switch position {
|
|
case .inRange(let x): return x
|
|
case .pastEnd: _preconditionFailure("Index out of range")
|
|
}
|
|
}
|
|
|
|
@inlinable
|
|
public subscript(bounds: Range<Index>)
|
|
-> Slice<ClosedRange<Bound>> {
|
|
return Slice(base: self, bounds: bounds)
|
|
}
|
|
|
|
@inlinable
|
|
public func _customContainsEquatableElement(_ element: Bound) -> Bool? {
|
|
return lowerBound <= element && element <= upperBound
|
|
}
|
|
|
|
@inlinable
|
|
public func _customIndexOfEquatableElement(_ element: Bound) -> Index?? {
|
|
return lowerBound <= element && element <= upperBound
|
|
? .inRange(element) : nil
|
|
}
|
|
|
|
@inlinable
|
|
public func _customLastIndexOfEquatableElement(_ element: Bound) -> Index?? {
|
|
// The first and last elements are the same because each element is unique.
|
|
return _customIndexOfEquatableElement(element)
|
|
}
|
|
|
|
/// Returns a Boolean value indicating whether the given range is contained
|
|
/// within this closed range.
|
|
///
|
|
/// The given range is contained within this closed range if the elements of
|
|
/// the range are all contained within this closed range.
|
|
///
|
|
/// let range = 0...10
|
|
/// range.contains(5..<7) // true
|
|
/// range.contains(5..<10) // true
|
|
/// range.contains(5..<12) // false
|
|
///
|
|
/// // Note that `5..<11` contains 5, 6, 7, 8, 9, and 10.
|
|
/// range.contains(5..<11) // true
|
|
///
|
|
/// Additionally, passing any empty range as `other` results in the value
|
|
/// `true`, even if the empty range's bounds are outside the bounds of this
|
|
/// closed range.
|
|
///
|
|
/// range.contains(3..<3) // true
|
|
/// range.contains(20..<20) // true
|
|
///
|
|
/// - Parameter other: A range to check for containment within this closed
|
|
/// range.
|
|
/// - Returns: `true` if `other` is empty or wholly contained within this
|
|
/// closed range; otherwise, `false`.
|
|
///
|
|
/// - Complexity: O(1)
|
|
@_alwaysEmitIntoClient
|
|
public func contains(_ other: Range<Bound>) -> Bool {
|
|
if other.isEmpty { return true }
|
|
let otherInclusiveUpper = other.upperBound.advanced(by: -1)
|
|
return lowerBound <= other.lowerBound && upperBound >= otherInclusiveUpper
|
|
}
|
|
}
|
|
|
|
extension ClosedRange {
|
|
/// Returns a Boolean value indicating whether the given closed range is
|
|
/// contained within this closed range.
|
|
///
|
|
/// The given closed range is contained within this range if its bounds are
|
|
/// contained within this closed range.
|
|
///
|
|
/// let range = 0...10
|
|
/// range.contains(2...5) // true
|
|
/// range.contains(2...10) // true
|
|
/// range.contains(2...12) // false
|
|
///
|
|
/// - Parameter other: A closed range to check for containment within this
|
|
/// closed range.
|
|
/// - Returns: `true` if `other` is wholly contained within this closed range;
|
|
/// otherwise, `false`.
|
|
///
|
|
/// - Complexity: O(1)
|
|
@_alwaysEmitIntoClient
|
|
public func contains(_ other: ClosedRange<Bound>) -> Bool {
|
|
lowerBound <= other.lowerBound && upperBound >= other.upperBound
|
|
}
|
|
}
|
|
|
|
extension Comparable {
|
|
/// Returns a closed range that contains both of its bounds.
|
|
///
|
|
/// Use the closed range operator (`...`) to create a closed range of any type
|
|
/// that conforms to the `Comparable` protocol. This example creates a
|
|
/// `ClosedRange<Character>` from "a" up to, and including, "z".
|
|
///
|
|
/// let lowercase = "a"..."z"
|
|
/// print(lowercase.contains("z"))
|
|
/// // Prints "true"
|
|
///
|
|
/// - Parameters:
|
|
/// - minimum: The lower bound for the range.
|
|
/// - maximum: The upper bound for the range.
|
|
///
|
|
/// - Precondition: `minimum <= maximum`.
|
|
@_transparent
|
|
public static func ... (minimum: Self, maximum: Self) -> ClosedRange<Self> {
|
|
_precondition(
|
|
minimum <= maximum, "Range requires lowerBound <= upperBound")
|
|
return ClosedRange(_uncheckedBounds: (lower: minimum, upper: maximum))
|
|
}
|
|
}
|
|
|
|
extension ClosedRange: Equatable {
|
|
/// Returns a Boolean value indicating whether two ranges are equal.
|
|
///
|
|
/// Two ranges are equal when they have the same lower and upper bounds.
|
|
///
|
|
/// let x = 5...15
|
|
/// print(x == 5...15)
|
|
/// // Prints "true"
|
|
/// print(x == 10...20)
|
|
/// // Prints "false"
|
|
///
|
|
/// - Parameters:
|
|
/// - lhs: A range to compare.
|
|
/// - rhs: Another range to compare.
|
|
@inlinable
|
|
public static func == (
|
|
lhs: ClosedRange<Bound>, rhs: ClosedRange<Bound>
|
|
) -> Bool {
|
|
return lhs.lowerBound == rhs.lowerBound && lhs.upperBound == rhs.upperBound
|
|
}
|
|
}
|
|
|
|
extension ClosedRange: Hashable where Bound: Hashable {
|
|
@inlinable
|
|
public func hash(into hasher: inout Hasher) {
|
|
hasher.combine(lowerBound)
|
|
hasher.combine(upperBound)
|
|
}
|
|
}
|
|
|
|
@_unavailableInEmbedded
|
|
extension ClosedRange: CustomStringConvertible {
|
|
/// A textual representation of the range.
|
|
@inlinable // trivial-implementation...
|
|
public var description: String {
|
|
return "\(lowerBound)...\(upperBound)"
|
|
}
|
|
}
|
|
|
|
@_unavailableInEmbedded
|
|
extension ClosedRange: CustomDebugStringConvertible {
|
|
/// A textual representation of the range, suitable for debugging.
|
|
public var debugDescription: String {
|
|
return "ClosedRange(\(String(reflecting: lowerBound))"
|
|
+ "...\(String(reflecting: upperBound)))"
|
|
}
|
|
}
|
|
|
|
#if SWIFT_ENABLE_REFLECTION
|
|
extension ClosedRange: CustomReflectable {
|
|
public var customMirror: Mirror {
|
|
return Mirror(
|
|
self, children: ["lowerBound": lowerBound, "upperBound": upperBound])
|
|
}
|
|
}
|
|
#endif
|
|
|
|
extension ClosedRange {
|
|
/// Returns a copy of this range clamped to the given limiting range.
|
|
///
|
|
/// The bounds of the result are always limited to the bounds of `limits`.
|
|
/// For example:
|
|
///
|
|
/// let x: ClosedRange = 0...20
|
|
/// print(x.clamped(to: 10...1000))
|
|
/// // Prints "10...20"
|
|
///
|
|
/// If the two ranges do not overlap, the result is a single-element range at
|
|
/// the upper or lower bound of `limits`.
|
|
///
|
|
/// let y: ClosedRange = 0...5
|
|
/// print(y.clamped(to: 10...1000))
|
|
/// // Prints "10...10"
|
|
///
|
|
/// - Parameter limits: The range to clamp the bounds of this range.
|
|
/// - Returns: A new range clamped to the bounds of `limits`.
|
|
@inlinable // trivial-implementation
|
|
@inline(__always)
|
|
public func clamped(to limits: ClosedRange) -> ClosedRange {
|
|
let lower =
|
|
limits.lowerBound > self.lowerBound ? limits.lowerBound
|
|
: limits.upperBound < self.lowerBound ? limits.upperBound
|
|
: self.lowerBound
|
|
let upper =
|
|
limits.upperBound < self.upperBound ? limits.upperBound
|
|
: limits.lowerBound > self.upperBound ? limits.lowerBound
|
|
: self.upperBound
|
|
return ClosedRange(_uncheckedBounds: (lower: lower, upper: upper))
|
|
}
|
|
}
|
|
|
|
extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
|
|
/// Creates an instance equivalent to the given `Range`.
|
|
///
|
|
/// - Parameter other: A `Range` to convert to a `ClosedRange` instance.
|
|
///
|
|
/// An equivalent range must be representable as a closed range.
|
|
/// For example, passing an empty range as `other` triggers a runtime error,
|
|
/// because an empty range cannot be represented by a closed range instance.
|
|
@inlinable
|
|
public init(_ other: Range<Bound>) {
|
|
_precondition(!other.isEmpty, "Can't form an empty closed range")
|
|
let upperBound = other.upperBound.advanced(by: -1)
|
|
self.init(_uncheckedBounds: (lower: other.lowerBound, upper: upperBound))
|
|
}
|
|
}
|
|
|
|
extension ClosedRange {
|
|
/// Returns a Boolean value indicating whether this range and the given closed
|
|
/// range contain an element in common.
|
|
///
|
|
/// This example shows two overlapping ranges:
|
|
///
|
|
/// let x: Range = 0...20
|
|
/// print(x.overlaps(10...1000))
|
|
/// // Prints "true"
|
|
///
|
|
/// - Parameter other: A range to check for elements in common.
|
|
/// - Returns: `true` if this range and `other` have at least one element in
|
|
/// common; otherwise, `false`.
|
|
@inlinable
|
|
public func overlaps(_ other: ClosedRange<Bound>) -> Bool {
|
|
// Disjoint iff the other range is completely before or after our range.
|
|
// Unlike a `Range`, a `ClosedRange` can *not* be empty, so no check for
|
|
// that case is needed here.
|
|
let isDisjoint = other.upperBound < self.lowerBound
|
|
|| self.upperBound < other.lowerBound
|
|
return !isDisjoint
|
|
}
|
|
|
|
/// Returns a Boolean value indicating whether this range and the given range
|
|
/// contain an element in common.
|
|
///
|
|
/// This example shows two overlapping ranges:
|
|
///
|
|
/// let x: Range = 0...20
|
|
/// print(x.overlaps(10..<1000))
|
|
/// // Prints "true"
|
|
///
|
|
/// Because a closed range includes its upper bound, the ranges in the
|
|
/// following example overlap:
|
|
///
|
|
/// let y = 20..<30
|
|
/// print(x.overlaps(y))
|
|
/// // Prints "true"
|
|
///
|
|
/// - Parameter other: A range to check for elements in common.
|
|
/// - Returns: `true` if this range and `other` have at least one element in
|
|
/// common; otherwise, `false`.
|
|
@inlinable
|
|
public func overlaps(_ other: Range<Bound>) -> Bool {
|
|
return other.overlaps(self)
|
|
}
|
|
}
|
|
|
|
// Note: this is not for compatibility only, it is considered a useful
|
|
// shorthand. TODO: Add documentation
|
|
public typealias CountableClosedRange<Bound: Strideable> = ClosedRange<Bound>
|
|
where Bound.Stride: SignedInteger
|
|
|
|
@_unavailableInEmbedded
|
|
extension ClosedRange: Decodable where Bound: Decodable {
|
|
public init(from decoder: Decoder) throws {
|
|
var container = try decoder.unkeyedContainer()
|
|
let lowerBound = try container.decode(Bound.self)
|
|
let upperBound = try container.decode(Bound.self)
|
|
guard lowerBound <= upperBound else {
|
|
throw DecodingError.dataCorrupted(
|
|
DecodingError.Context(
|
|
codingPath: decoder.codingPath,
|
|
debugDescription: "Cannot initialize \(ClosedRange.self) with a lowerBound (\(lowerBound)) greater than upperBound (\(upperBound))"))
|
|
}
|
|
self.init(_uncheckedBounds: (lower: lowerBound, upper: upperBound))
|
|
}
|
|
}
|
|
|
|
@_unavailableInEmbedded
|
|
extension ClosedRange: Encodable where Bound: Encodable {
|
|
public func encode(to encoder: Encoder) throws {
|
|
var container = encoder.unkeyedContainer()
|
|
try container.encode(self.lowerBound)
|
|
try container.encode(self.upperBound)
|
|
}
|
|
}
|
|
|
|
extension ClosedRange: Sendable where Bound: Sendable { }
|
|
extension ClosedRange.Index: Sendable where Bound: Sendable { }
|