Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contains(_:) methods to (Closed)Range #76891

Merged
merged 7 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ set(SWIFT_BENCH_MODULES
single-source/RandomTree
single-source/RandomValues
single-source/RangeAssignment
single-source/RangeContains
single-source/RangeIteration
single-source/RangeOverlaps
single-source/RangeReplaceableCollectionPlusDefault
Expand Down
96 changes: 96 additions & 0 deletions benchmark/single-source/RangeContains.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//===--- RangeContains.swift ----------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2024 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
//
//===----------------------------------------------------------------------===//

import TestsUtils

public let benchmarks = [
BenchmarkInfo(
name: "RangeContainsRange",
runFunction: run_RangeContainsRange,
tags: [.validation, .api],
setUpFunction: buildRanges),
BenchmarkInfo(
name: "RangeContainsClosedRange",
runFunction: run_RangeContainsClosedRange,
tags: [.validation, .api],
setUpFunction: buildRanges),
BenchmarkInfo(
name: "ClosedRangeContainsRange",
runFunction: run_ClosedRangeContainsRange,
tags: [.validation, .api],
setUpFunction: buildRanges),
BenchmarkInfo(
name: "ClosedRangeContainsClosedRange",
runFunction: run_ClosedRangeContainsClosedRange,
tags: [.validation, .api],
setUpFunction: buildRanges),
]

private func buildRanges() {
blackHole(ranges)
blackHole(closedRanges)
}

private let ranges: [Range<Int>] = (-8...8).flatMap { a in (0...16).map { l in a..<(a+l) } }
private let closedRanges: [ClosedRange<Int>] = (-8...8).flatMap { a in (0...16).map { l in a...(a+l) } }

@inline(never)
public func run_RangeContainsRange(_ n: Int) {
var checksum: UInt64 = 0
for _ in 0..<n {
for lhs in ranges {
for rhs in ranges {
if lhs.contains(rhs) { checksum += 1 }
}
}
}
check(checksum == 15725 * UInt64(n))
}

@inline(never)
public func run_RangeContainsClosedRange(_ n: Int) {
var checksum: UInt64 = 0
for _ in 0..<n {
for lhs in ranges {
for rhs in closedRanges {
if lhs.contains(rhs) { checksum += 1 }
}
}
}
check(checksum == 10812 * UInt64(n))
}

@inline(never)
public func run_ClosedRangeContainsRange(_ n: Int) {
var checksum: UInt64 = 0
for _ in 0..<n {
for lhs in closedRanges {
for rhs in ranges {
if lhs.contains(rhs) { checksum += 1 }
}
}
}
check(checksum == 17493 * UInt64(n))
}

@inline(never)
public func run_ClosedRangeContainsClosedRange(_ n: Int) {
var checksum: UInt64 = 0
for _ in 0..<n {
for lhs in closedRanges {
for rhs in closedRanges {
if lhs.contains(rhs) { checksum += 1 }
}
}
}
check(checksum == 12597 * UInt64(n))
}
2 changes: 2 additions & 0 deletions benchmark/utils/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ import RandomShuffle
import RandomTree
import RandomValues
import RangeAssignment
import RangeContains
import RangeIteration
import RangeOverlaps
import RangeReplaceableCollectionPlusDefault
Expand Down Expand Up @@ -362,6 +363,7 @@ register(RandomShuffle.benchmarks)
register(RandomTree.benchmarks)
register(RandomValues.benchmarks)
register(RangeAssignment.benchmarks)
register(RangeContains.benchmarks)
register(RangeIteration.benchmarks)
register(RangeOverlaps.benchmarks)
register(RangeReplaceableCollectionPlusDefault.benchmarks)
Expand Down
89 changes: 89 additions & 0 deletions stdlib/public/core/ClosedRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,64 @@ where Bound: Strideable, Bound.Stride: SignedInteger
// 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 {
Expand Down Expand Up @@ -459,6 +517,18 @@ extension ClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
}

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.
Expand All @@ -469,6 +539,25 @@ extension ClosedRange {
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)
Expand Down
79 changes: 78 additions & 1 deletion stdlib/public/core/Range.swift
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ extension Range {
/// This example shows two overlapping ranges:
///
/// let x: Range = 0..<20
/// print(x.overlaps(10...1000))
/// print(x.overlaps(10..<1000))
/// // Prints "true"
///
/// Because a half-open range does not include its upper bound, the ranges
Expand All @@ -1011,6 +1011,25 @@ extension Range {
return !isDisjoint
}

/// 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"
///
/// Because a half-open range does not include its upper bound, the ranges
/// in the following example do not overlap:
///
/// let y = 20...30
/// print(x.overlaps(y))
/// // Prints "false"
///
/// - Parameter other: A closed 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.
Expand All @@ -1024,6 +1043,64 @@ extension Range {
}
}

extension Range {
/// Returns a Boolean value indicating whether the given range is contained
/// within this range.
///
/// The given range is contained within this range if its bounds are equal to
/// or within the bounds of this range.
///
/// let range = 0..<10
/// range.contains(2..<5) // true
/// range.contains(2..<10) // true
/// range.contains(2..<12) // false
///
/// 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
/// range.
///
/// let emptyRange = 3..<3
/// emptyRange.contains(3..<3) // true
/// emptyRange.contains(5..<5) // true
///
/// - Parameter other: A range to check for containment within this range.
/// - Returns: `true` if `other` is empty or wholly contained within this
/// range; otherwise, `false`.
///
/// - Complexity: O(1)
@_alwaysEmitIntoClient
public func contains(_ other: Range<Bound>) -> Bool {
other.isEmpty ||
(lowerBound <= other.lowerBound && upperBound >= other.upperBound)
}

/// Returns a Boolean value indicating whether the given closed range is
/// contained within this range.
///
/// The given closed range is contained within this range if its bounds are
/// contained within this range. If this range is empty, it cannot contain a
/// closed range, since closed ranges by definition contain their boundaries.
///
/// let range = 0..<10
/// range.contains(2...5) // true
/// range.contains(2...10) // false
/// range.contains(2...12) // false
///
/// let emptyRange = 3..<3
/// emptyRange.contains(3...3) // false
///
/// - Parameter other: A closed range to check for containment within this
/// range.
/// - Returns: `true` if `other` is wholly contained within this range;
/// otherwise, `false`.
///
/// - Complexity: O(1)
@_alwaysEmitIntoClient
public func contains(_ other: ClosedRange<Bound>) -> Bool {
lowerBound <= other.lowerBound && upperBound > other.upperBound
}
}

// Note: this is not for compatibility only, it is considered a useful
// shorthand. TODO: Add documentation
public typealias CountableRange<Bound: Strideable> = Range<Bound>
Expand Down
Loading