Skip to content

Commit

Permalink
Merge pull request #8 from RougeWare/develop
Browse files Browse the repository at this point in the history
Sync develop into master
  • Loading branch information
KyLeggiero authored May 20, 2020
2 parents 990d5d0 + 166f21e commit 4d1ce51
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 6 deletions.
95 changes: 95 additions & 0 deletions Sources/SafeCollectionAccess/Ranges or nil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
//
// Ranges or nil.swift
// Safe Collection Access
//
// Created by Ben Leggiero on 2020-05-20.
// Copyright © 2020 Ben Leggiero BH-1-PS.
//

import Foundation



public extension RandomAccessCollection
where Index: Strideable,
Index.Stride: SignedInteger
{
/// Safely access ranges in this collection.
/// If the range you pass extends outside this collection, `nil` is returned.
///
/// - Complexity: O(n)
///
/// - Parameter range: A range of valid lower and upper indices of characters in the string.
/// - Returns: The characters at the given index-range in this string, or `nil` if the given range is not contained
/// within `0` (inclusive) and `count` (exclusive).
@inlinable
subscript(orNil range: ClosedRange<Index>) -> SubSequence? {
return contains(allIn: range)
? self[range]
: nil
}


/// Safely access ranges in this collection.
/// If the range you pass extends outside this collection, `nil` is returned.
///
/// - Complexity: O(n)
///
/// - Parameter range: A range of valid lower and upper indices of characters in the string.
/// - Returns: The characters at the given index-range in this string, or `nil` if the given range is not contained
/// within `0` (inclusive) and `count` (inclusive).
@inlinable
subscript(orNil range: Range<Index>) -> SubSequence? {
return contains(allIn: range)
? self[range]
: nil
}


/// Safely access ranges in this collection.
/// If the range you pass extends outside this collection, `nil` is returned.
///
/// - Complexity: O(1)
///
/// - Parameter range: A range of a valid lower index of characters in the string.
/// - Returns: The characters at the given index-range in this string, or `nil` if the given range is not contained
/// within `0` (inclusive) and `count` (exclusive).
@inlinable
subscript(orNil range: PartialRangeFrom<Index>) -> SubSequence? {
return contains(index: range.lowerBound)
? self[orNil: Range(uncheckedBounds: (lower: range.lowerBound, upper: endIndex))]
: nil
}


/// Safely access ranges in this collection.
/// If the range you pass extends outside this collection, `nil` is returned.
///
/// - Complexity: O(1)
///
/// - Parameter range: A range of a valid upper index of characters in the string.
/// - Returns: The characters at the given index-range in this string, or `nil` if the given range's bound is not
/// within `0` (inclusive) and `count` (inclusive).
@inlinable
subscript(orNil range: PartialRangeUpTo<Index>) -> SubSequence? {
return contains(index: index(before: range.upperBound))
? self[orNil: startIndex ..< range.upperBound]
: nil
}


/// Safely access ranges in this collection.
/// If the range you pass extends outside this collection, `nil` is returned.
///
/// - Complexity: O(1)
///
/// - Parameter range: A range of a valid upper index of characters in the string.
/// - Returns: The characters at the given index-range in this string, or `nil` if the given range's bound is not
/// within `0` (inclusive) and `count` (exclusive).
@inlinable
subscript(orNil range: PartialRangeThrough<Index>) -> SubSequence? {
return contains(index: range.upperBound)
? self[orNil: startIndex ... range.upperBound]
: nil
}
}
35 changes: 33 additions & 2 deletions Sources/SafeCollectionAccess/Safe Collection Access.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@

public extension RandomAccessCollection {

/// Determines whether this collection has an item at the given index
///
/// - Parameter index: An index that might be in this collection
/// - Returns: `true` iff `index` points to an item that's in this collection
func contains(index: Index) -> Bool {
indices.contains(index)
}


/// Determines whether this collection contains all the indices in the given index
///
/// This is a naïve iteration over all the indices in this collection
///
/// - Complexity: O(n)
///
/// - Parameter otherIndices: Some indices which might point to items in this collection
/// - Returns: `false` if any of the given indices
func contains<Indices>(allIn otherIndices: Indices) -> Bool
where Indices: Sequence,
Indices.Element == Index
{
otherIndices.allSatisfy { indices.contains($0) }
}


/// Safely access this collection. If the index you pass is not in this collection, then `nil` is returned.
///
/// If you don't like the `orNil` syntax, you may also use `[safe:]`.
Expand All @@ -20,19 +45,25 @@ public extension RandomAccessCollection {
/// - Returns: The element which is in this collection at the given index, or `nil` if it's outside this collection
@inlinable
subscript(orNil index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
return contains(index: index)
? self[index]
: nil
}


/// - Deprecated: Use a subscript with a more descriptive label, like `[orNil:]` or `[clamping:]`
///
/// Safely access this collection. If the index you pass is not in this collection, then `nil` is returned.
///
/// This is an inlined alias to `[orNil:]`.
///
///
/// - Parameter index: The index of the element to retrieve, or an index outside this collection
/// - Returns: The element which is in this collection at the given index, or `nil` if it's outside this collection
@inline(__always)
@available(swift, deprecated: 0.0.1, message: "Deprecated in SafeCollectionAccess 1.2.0: Since there is no clear behavior of a 'safe' subscript, use the one which best describes your target behavior, like `[orNil:]` or `[clamping:]`")
subscript(safe index: Index) -> Element? {
return self[orNil: index]
self[orNil: index]
}
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import XCTest
import SafeCollectionAccessTests

var tests = [XCTestCaseEntry]()
tests += SafeCollectionAccessTests.allTests()
tests += SafeCollectionAccessTests.allTests
tests += RangeOrNilTests.allTests
XCTMain(tests)
109 changes: 109 additions & 0 deletions Tests/SafeCollectionAccessTests/RangesOrNilTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// RangesOrNilTests.swift
// Safe Collection Access
//
// Created by Ben Leggiero on 2020-05-20.
// Copyright © 2020 Ben Leggiero BH-1-PS.
//

import XCTest
@testable import SafeCollectionAccess



final class RangeOrNilTests: XCTestCase {

let first5Fibonacci = [1, 1, 2, 3, 5]


func testSubscriptOrNil_Range() {
XCTAssertEqual(first5Fibonacci[orNil: 0..<5], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 1..<5], [1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 2..<5], [2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 3..<5], [3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 4..<5], [5])
XCTAssertEqual(first5Fibonacci[orNil: 5..<5], [])

XCTAssertEqual(first5Fibonacci[orNil: 0..<5], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 0..<4], [1, 1, 2, 3])
XCTAssertEqual(first5Fibonacci[orNil: 0..<3], [1, 1, 2])
XCTAssertEqual(first5Fibonacci[orNil: 0..<2], [1, 1])
XCTAssertEqual(first5Fibonacci[orNil: 0..<1], [1])
XCTAssertEqual(first5Fibonacci[orNil: 0..<0], [])

XCTAssertNil(first5Fibonacci[orNil: -1 ..< 0])
XCTAssertNil(first5Fibonacci[orNil: 0 ..< 6])
XCTAssertNil(first5Fibonacci[orNil: -20 ..< -10])
XCTAssertNil(first5Fibonacci[orNil: 7 ..< 9])
}


func testSubscriptOrNil_ClosedRange() {
XCTAssertEqual(first5Fibonacci[orNil: 0...4], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 1...4], [1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 2...4], [2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 3...4], [3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 4...4], [5])

XCTAssertEqual(first5Fibonacci[orNil: 0...4], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 0...3], [1, 1, 2, 3])
XCTAssertEqual(first5Fibonacci[orNil: 0...2], [1, 1, 2])
XCTAssertEqual(first5Fibonacci[orNil: 0...1], [1, 1])
XCTAssertEqual(first5Fibonacci[orNil: 0...0], [1])

XCTAssertNil(first5Fibonacci[orNil: -1 ... 0])
XCTAssertNil(first5Fibonacci[orNil: 0 ... 6])
XCTAssertNil(first5Fibonacci[orNil: -20 ... -10])
XCTAssertNil(first5Fibonacci[orNil: 7 ... 9])
}


func testSubscriptOrNil_PartialRangeFrom() {
XCTAssertEqual(first5Fibonacci[orNil: 0...], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 1...], [1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 2...], [2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 3...], [3, 5])
XCTAssertEqual(first5Fibonacci[orNil: 4...], [5])

XCTAssertNil(first5Fibonacci[orNil: ( 5)...])
XCTAssertNil(first5Fibonacci[orNil: ( -1)...])
XCTAssertNil(first5Fibonacci[orNil: (-20)...])
XCTAssertNil(first5Fibonacci[orNil: ( 7)...])
}


func testSubscriptOrNil_PartialRangeUpTo() {
XCTAssertEqual(first5Fibonacci[orNil: ..<5], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: ..<4], [1, 1, 2, 3])
XCTAssertEqual(first5Fibonacci[orNil: ..<3], [1, 1, 2])
XCTAssertEqual(first5Fibonacci[orNil: ..<2], [1, 1])
XCTAssertEqual(first5Fibonacci[orNil: ..<1], [1])

XCTAssertNil(first5Fibonacci[orNil: ..<( 0 )])
XCTAssertNil(first5Fibonacci[orNil: ..<( 6 )])
XCTAssertNil(first5Fibonacci[orNil: ..<(-10)])
XCTAssertNil(first5Fibonacci[orNil: ..<( 9 )])
}


func testSubscriptOrNil_PartialRangeUpThrough() {
XCTAssertEqual(first5Fibonacci[orNil: ...4], [1, 1, 2, 3, 5])
XCTAssertEqual(first5Fibonacci[orNil: ...3], [1, 1, 2, 3])
XCTAssertEqual(first5Fibonacci[orNil: ...2], [1, 1, 2])
XCTAssertEqual(first5Fibonacci[orNil: ...1], [1, 1])
XCTAssertEqual(first5Fibonacci[orNil: ...0], [1])

XCTAssertNil(first5Fibonacci[orNil: ...( 6 )])
XCTAssertNil(first5Fibonacci[orNil: ...(-10)])
XCTAssertNil(first5Fibonacci[orNil: ...( 9 )])
}


static var allTests = [
("testSubscriptOrNil_Range", testSubscriptOrNil_Range),
("testSubscriptOrNil_ClosedRange", testSubscriptOrNil_ClosedRange),
("testSubscriptOrNil_PartialRangeFrom", testSubscriptOrNil_PartialRangeFrom),
("testSubscriptOrNil_PartialRangeUpTo", testSubscriptOrNil_PartialRangeUpTo),
("testSubscriptOrNil_PartialRangeUpThrough", testSubscriptOrNil_PartialRangeUpThrough),
]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Safe Collection Access.swift
// SafeCollectionAccessTests.swift
// Safe Collection Access
//
// Created by Ben Leggiero on 2019-12-06.
Expand Down Expand Up @@ -41,7 +41,8 @@ final class SafeCollectionAccessTests: XCTestCase {
}


func testSubscriptSafe() {
@available(swift, deprecated: 0.0.1, message: "Deprecated in 1.2.0")
func testSubscriptSafe() { // TODO: Remove in 2.0.0
XCTAssertEqual(1, first5Fibonacci[safe: 0])
XCTAssertEqual(1, first5Fibonacci[safe: 1])
XCTAssertEqual(2, first5Fibonacci[safe: 2])
Expand Down Expand Up @@ -111,7 +112,6 @@ final class SafeCollectionAccessTests: XCTestCase {

static var allTests = [
("testSubscriptOrNil", testSubscriptOrNil),
("testSubscriptSafe", testSubscriptSafe),
("testSubscriptClamping", testSubscriptClamping),
]
}
1 change: 1 addition & 0 deletions Tests/SafeCollectionAccessTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import XCTest
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(SafeCollectionAccessTests.allTests),
testCase(RangeOrNilTests.allTests),
]
}
#endif

0 comments on commit 4d1ce51

Please sign in to comment.