Intersection of ranges (of floating point ranges) - swift

I am asking a user for four ranges of floating point numbers. I want to check that there is no overlap between them.
If the ranges were integer ranges it seems that I could either create sets or use Swift Range (or NSRange) and check for intersections.
Is there a way to figure this out if the ranges where then upper and lower bounds are floating point values?
Would I just have to check that each lower and upper bound of each range is not between the lower/upper bound of each of the other ranges? Is there a better way?
Thanks

You can use the Range type to represent a range of floating point values. You can then use the contains method to check if a value is in a range. You can also use the overlaps method to check if two ranges overlap. Here is an example:
let range1 = 1.0..<2.0
let range2 = 1.5..<3.0
let range3 = 2.0..<4.0
range1.contains(1.5) // true
range1.overlaps(range2) // true
range1.overlaps(range3) // false
You can also use the ~= operator to check if a value is in a range. This is the same operator that is used in switch statements.
let value = 1.5
switch value {
case range1:
if range1.contains(value) {
print("value is in range1")
}
case range2:
if range2.contains(value) {
print("value is in range2")
}
case range3:
if range3.contains(value) {
print("value is in range3")
}
default:
print("value is not in any range")
}
You can use the sorted method to sort an array of ranges. Here is an example:
let ranges = [range1, range2, range3]
let sortedRanges = ranges.sorted { $0.lowerBound < $1.lowerBound }
You can then iterate over the sorted ranges and check if the upper bound of the previous range is greater than the lower bound of the current range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if previousRange.upperBound > range.lowerBound {
print("ranges overlap")
}
}
}
You can also use the contains/overlaps method to check if a range contains another range.
for (index, range) in sortedRanges.enumerated() {
if index > 0 {
let previousRange = sortedRanges[index - 1]
if range.contains(previousRange) {
print("ranges overlap")
}
if range.overlaps(previousRange) {
print("ranges overlap")
}
}
}

There is no way to express it as a property yet; it has to be a function.
import Algorithms
extension Sequence {
func containsAnOverlap<Bound>() -> Bool
where Element == ClosedRange<Bound> {
sorted(by: \.lowerBound).adjacentPairs().contains { $0.overlaps($1) }
}
}
public extension Sequence {
/// Sorted by a common `Comparable` value.
func sorted(
by comparable: (Element) throws -> some Comparable
) rethrows -> [Element] {
try sorted(by: comparable, <)
}
/// Sorted by a common `Comparable` value, and sorting closure.
func sorted<Comparable: Swift.Comparable>(
by comparable: (Element) throws -> Comparable,
_ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
) rethrows -> [Element] {
try sorted {
try areInIncreasingOrder(comparable($0), comparable($1))
}
}
}
[2...3.0, 0...1.0].containsAnOverlap() // false
[1...3.0, 0...2.0].containsAnOverlap() // true

Related

Data.subdata(in:) results in EXC_BAD_INSTRUCTION

While trying to retrieve subdata of a Data object, the application crashes issuing the following error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
Below you can see the code. It's a Data extension. Hope someone can explain why this crashes.
public extension Data {
/// Removes and returns the range of data at the specified position.
/// - Parameter range: The range to remove. `range` must be valid
/// for the collection and should not exceed the collection's end index.
/// - Returns: The removed data.
mutating func remove(at range: Range<Data.Index>) -> Self {
precondition(range.lowerBound >= 0, "Range invalid, lower bound cannot be below 0")
precondition(range.upperBound < self.count, "Range invalid, upper bound exceeds data size")
let removal = subdata(in: range) // <- Error occurs here
removeSubrange(range)
return removal
}
}
EDIT - added the caller functions:
This extension is called from the following function:
func temporary(data: inout Data) -> Data {
let _ = data.removeFirst()
return data.remove(range: 0 ..< 3)
}
Which in turn is called like this:
var data = Data([0,1,2,3,4,5])
let subdata = temporary(data: &data)
You haven't provided enough information for us to know the reason of your crash. One thing that I know that is wrong in your method is your precondition. You wont be able to pass a range to remove all elements of your collection. Besides that you should implement a generic method that would take a RangeExpression instead of a Range. This is how I would implement such method:
extension Data {
/// Removes and returns the range of data at the specified position.
/// - Parameter range: The range to remove. `range` must be valid
/// for the collection and should not exceed the collection's end index.
/// - Returns: The removed data.
mutating func remove<R>(_ range: R) -> Data where R: RangeExpression, Index == R.Bound {
defer { removeSubrange(range) }
return subdata(in: range.relative(to: self))
}
}
Usage:
var data = Data([0,1,2,3,4,5])
let subdata = data.remove(0..<6)
print(Array(data), Array(subdata)) // "[] [0, 1, 2, 3, 4, 5]\n"
To check if your data indices contains a specific range before attempting to remove you can use pattern-matching operator:
var data = Data([0,1,2,3,4,5])
let range = 0..<7
if data.indices ~= range {
let subdata = data.remove(range)
print(Array(data), Array(subdata))
} else {
print("invalid subrange") // "invalid subrange\n"
}
If you would like to do the same with a ClosedRange you would need to implement your own pattern-matching operator on Range:
extension Range {
static func ~=(lhs: Self, rhs: ClosedRange<Bound>) -> Bool {
lhs.contains(rhs.lowerBound) && lhs.contains(rhs.upperBound)
}
}
Usage:
var data = Data([0,1,2,3,4,5])
let range = 0...5
if data.indices ~= range {
let subdata = data.remove(range)
print(Array(data), Array(subdata)) // "[] [0, 1, 2, 3, 4, 5]\n"
} else {
print("invalid subrange")
}
The error is caused by the removeFirst function. The documentation clearly states:
Calling this method may invalidate all saved indices of this collection. Do not rely on a previously stored index value after altering a collection with any operation that can change its length.
It appears that is exactly what is causing my error. I have replaced removeFirst with remove(at:) and it now works.

High order functions assignation

I'm learning about High Order functions in Swift (like .map .filter .reduce...) and generics types.
Here is my function :
func max<T: Comparable>(_ array: [T]) -> T {
var max = 0 as! T
for value in array {
if value > max { max = value }
}
return max
}
How can I replace my for loop with high order function to get the same result ?
Im looking to do something like this (or better) :
max = array.map { $0 > max ? $0 : max }
Reduce!
return array.reduce(nil)
{
(max: T?, current: T) -> T? in
guard let max = max else { return current }
return max > current ? max : current
}
That will return an optional but that is probably sensible given you might pass in an empty array.
Of course there is also this
https://developer.apple.com/documentation/swift/array/1688806-max
The implication of tyour question is that this is a learning exercise. So here is a generalisation of the solution that makes use of higher order functions. Note that the Swft Strandard Library already contains a function that does this.
extension Array
{
func pickOne(choose: (Element, Element) -> Element) -> Element?
{
return self.reduce(nil)
{
(bestSoFar: Element?, current: Element) -> Element? in
guard let bestSoFar = bestSoFar else { return current }
return choose(bestSoFar, current)
}
}
}
So the functionality of max is now defined like this:
array.pickOne { $0 > $1 ? $0 : $1 }
and min would be
array.pickOne { $0 < $1 ? $0 : $1 }
First note that your approach for the "initial value" and the forced
cast
var max = 0 as! T
has two problems:
It will crash for arrays not containing integers, e.g. max(["a", "b"]).
Even for integer arrays, it is wrong if all array elements are
negative, e.g. max([-2, -3]) should be -2 and not zero.
So you better choose the first array element as initial value instead
of the "forced zero".
That leads to the next question: What if the array
is empty? There are two valid approaches: You can require that the
function is called with a non-empty array (and document that
precondition):
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
var max = array[0]
for value in array {
if value > max { max = value }
}
return max
}
Or (as also suggested in the other answers) make the return value
optional:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard var max = array.first else { return nil }
for value in array {
if value > max { max = value }
}
return max
}
Both approaches can be implemented with reduce().
The first one would be
/// Compute the maximal element in an array.
///
/// - Returns: The maximal element.
///
/// - Note: The array must not be empty.
func max<T: Comparable>(_ array: [T]) -> T {
precondition(!array.isEmpty, "`max` called with empty array")
return array.reduce(array[0]) { $0 > $1 ? $0 : $1 }
}
and the second one
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
guard let first = array.first else { return nil }
return array.reduce(first) { $0 > $1 ? $0 : $1 }
}
This can be further shortened using the flatMap() method
of Optional:
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0) { $0 > $1 ? $0 : $1 } }
}
Finally you can use the existing
func max<T : Comparable>(_ x: T, _ y: T) -> T
function instead of a literal closure in all of the above examples, e.g.
/// Compute the maximal element in an array.
///
/// - Returns: `nil` if the array is empty, and the maximal element otherwise.
func max<T: Comparable>(_ array: [T]) -> T? {
return array.first.flatMap { array.reduce($0, max) }
}
For Ints you would want to use reduce for this like so:
// Reduce with initial value the first value of the array if available,
// or 0 otherwise
let max = array.reduce(array.first ?? 0) { (max, newValue) -> T in
return newValue > max ? newValue : max
}
UPDATE
You want JeremyP's answer for a proper handling of all Comparable!

Is it possible to cut short evaluation of a higher-level function?

I am looking for a way to stop a higher-level function after evaluating part of its input sequence.
Consider a situation when you look for the first index in a sequence that satisfies a certain condition. For example, let's say we are looking for the first position in an array a of Ints where the sum of two consecutive values is above 100.
You can do it with a loop, like this:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
for i in 0..<a.count-1 {
if a[i]+a[i+1] > 100 {
return i
}
}
return nil
}
The looping stops as soon as the position of interest is discovered.
We can rewrite this code using reduce as follows:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
return (0..<a.count-1).reduce(nil) { prev, i in
prev ?? (a[i]+a[i+1] > 100 ? i : nil)
}
}
However, the disadvantage of this approach is that reduce goes all the way up to a.count-2 even if it finds a match at the very first index. The result is going to be the same, but it would be nice to cut the unnecessary work.
Is there a way to make reduce stop trying further matches, or perhaps a different function that lets you stop after finding the first match?
As already said, reduce is specifically designed in order to evaluate an entire sequence and therefore not designed to short-circuit. Using it in this way to find an the index of an element that meets a given predicate is best done with indexOf as #Casey says.
Also as of Swift 3, there is now a first(where:) function on Sequence that allows you to find the first element that satisfies a given predicate. This could be an even more suitable alternative than indexOf, as it returns the element instead of the index (although in your particular example these are the same).
You could write your example like this:
func firstAbove100(_ a:[Int]) -> Int? {
guard a.count > 1 else {return nil}
return (0..<a.count-1).first { i in
a[i]+a[i+1] > 100
}
}
However if you want a more general high level function that will iterate through a sequence and break out if it finds a non-nil result of a given predicate – you could always write your own find function:
extension SequenceType {
func find<T>(#noescape predicate: (Self.Generator.Element) throws -> T?) rethrows -> T? {
for element in self {
if let c = try predicate(element) {return c}
}
return nil
}
}
You could now write your firstAbove100 function like this:
func firstAbove100(a:[Int]) -> Int? {
if a.count < 2 {
return nil
}
return (0..<a.count-1).find { i in
a[i]+a[i+1] > 100 ? i : nil
}
}
and it will now short-circuit when it finds a pair of elements that add to above 100.
Or let's say instead of returning the index of the first pair of elements in your array that add to greater than 100, you now want to return the sum of the elements. You could now write it like this:
func sumOfFirstAbove100(a:[Int]) -> Int? {
guard a.count > 1 else {return nil}
return (0..<a.count-1).find { i in
let sum = a[i]+a[i+1]
return sum > 100 ? sum : nil
}
}
let a = [10, 20, 30, 40, 50, 60, 70, 80, 90]
print(sumOfFirstAbove100(a)) // prints: Optional(110)
The find function will iterate through the array, applying the predicate to each element (in this case the indices of your array). If the predicate returns nil, then it will carry on iterating. If the predicate returns non-nil, then it will return that result and stop iterating.
indexOf will stop after it finds the first match so you might rewrite firstAbove100 to something like this:
func firstAbove100(a:[Int]) -> Int? {
return a.count > 1 ? (a.startIndex..<a.endIndex-1).indexOf({ a[$0] + a[$0 + 1] > 100 }) : nil
}

Convert String.CharacterView.Index to int [duplicate]

I want to convert the index of a letter contained within a string to an integer value. Attempted to read the header files but I cannot find the type for Index, although it appears to conform to protocol ForwardIndexType with methods (e.g. distanceTo).
var letters = "abcdefg"
let index = letters.characters.indexOf("c")!
// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index) // I want the integer value of the index (e.g. 2)
Any help is appreciated.
edit/update:
Xcode 11 • Swift 5.1 or later
extension StringProtocol {
func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}
extension Collection {
func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}
extension String.Index {
func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}
Playground testing
let letters = "abcdefg"
let char: Character = "c"
if let distance = letters.distance(of: char) {
print("character \(char) was found at position #\(distance)") // "character c was found at position #2\n"
} else {
print("character \(char) was not found")
}
let string = "cde"
if let distance = letters.distance(of: string) {
print("string \(string) was found at position #\(distance)") // "string cde was found at position #2\n"
} else {
print("string \(string) was not found")
}
Works for Xcode 13 and Swift 5
let myString = "Hello World"
if let i = myString.firstIndex(of: "o") {
let index: Int = myString.distance(from: myString.startIndex, to: i)
print(index) // Prints 4
}
The function func distance(from start: String.Index, to end: String.Index) -> String.IndexDistance returns an IndexDistance which is just a typealias for Int
Swift 4
var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Note: If String contains same multiple characters, it will just get the nearest one from left
var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
encodedOffset has deprecated from Swift 4.2.
Deprecation message:
encodedOffset has been deprecated as most common usage is incorrect. Use utf16Offset(in:) to achieve the same behavior.
So we can use utf16Offset(in:) like this:
var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
When searching for index like this
⛔️ guard let index = (positions.firstIndex { position <= $0 }) else {
it is treated as Array.Index. You have to give compiler a clue you want an integer
✅ guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Swift 5
You can do convert to array of characters and then use advanced(by:) to convert to integer.
let myString = "Hello World"
if let i = Array(myString).firstIndex(of: "o") {
let index: Int = i.advanced(by: 0)
print(index) // Prints 4
}
To perform string operation based on index , you can not do it with traditional index numeric approach. because swift.index is retrieved by the indices function and it is not in the Int type. Even though String is an array of characters, still we can't read element by index.
This is frustrating.
So ,to create new substring of every even character of string , check below code.
let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
if i % 2 == 0 {
resultStrArray.append(mystrArray[i])
}
i += 1
}
let resultString = String(resultStrArray)
print(resultString)
Output : acegikmoqsuwy
Thanks In advance
Here is an extension that will let you access the bounds of a substring as Ints instead of String.Index values:
import Foundation
/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
/// Access the range of the search string as integer indices
/// in the rendered string.
/// - NOTE: This is "unsafe" because it may not return what you expect if
/// your string contains single symbols formed from multiple scalars.
/// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
/// from the result of the standard function range(of:).
func countableRange<SearchType: StringProtocol>(
of search: SearchType,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> CountableRange<Int>? {
guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
return nil
}
let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart
return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
}
}
Just be aware that this can lead to weirdness, which is why Apple has chosen to make it hard. (Though that's a debatable design decision - hiding a dangerous thing by just making it hard...)
You can read more in the String documentation from Apple, but the tldr is that it stems from the fact that these "indices" are actually implementation-specific. They represent the indices into the string after it has been rendered by the OS, and so can shift from OS-to-OS depending on what version of the Unicode spec is being used. This means that accessing values by index is no longer a constant-time operation, because the UTF spec has to be run over the data to determine the right place in the string. These indices will also not line up with the values generated by NSString, if you bridge to it, or with the indices into the underlying UTF scalars. Caveat developer.
In case you got an "index is out of bounds" error. You may try this approach. Working in Swift 5
extension String{
func countIndex(_ char:Character) -> Int{
var count = 0
var temp = self
for c in self{
if c == char {
//temp.remove(at: temp.index(temp.startIndex,offsetBy:count))
//temp.insert(".", at: temp.index(temp.startIndex,offsetBy: count))
return count
}
count += 1
}
return -1
}
}

Swift Array - Check if an index exists

In Swift, is there any way to check if an index exists in an array without a fatal error being thrown?
I was hoping I could do something like this:
let arr: [String] = ["foo", "bar"]
let str: String? = arr[1]
if let str2 = arr[2] as String? {
// this wouldn't run
println(str2)
} else {
// this would be run
}
But I get
fatal error: Array index out of range
An elegant way in Swift:
let isIndexValid = array.indices.contains(index)
Type extension:
extension Collection {
subscript(optional i: Index) -> Iterator.Element? {
return self.indices.contains(i) ? self[i] : nil
}
}
Using this you get an optional value back when adding the keyword optional to your index which means your program doesn't crash even if the index is out of range. In your example:
let arr = ["foo", "bar"]
let str1 = arr[optional: 1] // --> str1 is now Optional("bar")
if let str2 = arr[optional: 2] {
print(str2) // --> this still wouldn't run
} else {
print("No string found at that index") // --> this would be printed
}
Just check if the index is less than the array size:
if 2 < arr.count {
...
} else {
...
}
Add some extension sugar:
extension Collection {
subscript(safe index: Index) -> Iterator.Element? {
guard indices.contains(index) else { return nil }
return self[index]
}
}
if let item = ["a", "b", "c", "d"][safe: 3] { print(item) } // Output: "d"
// or with guard:
guard let anotherItem = ["a", "b", "c", "d"][safe: 3] else { return }
print(anotherItem) // "d"
Enhances readability when doing if let style coding in conjunction with arrays
the best way.
let reqIndex = array.indices.contains(index)
print(reqIndex)
Swift 4 extension:
For me i prefer like method.
// MARK: - Extension Collection
extension Collection {
/// Get at index object
///
/// - Parameter index: Index of object
/// - Returns: Element at index or nil
func get(at index: Index) -> Iterator.Element? {
return self.indices.contains(index) ? self[index] : nil
}
}
Thanks to #Benno Kress
You can rewrite this in a safer way to check the size of the array, and use a ternary conditional:
if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
Asserting if an array index exist:
This methodology is great if you don't want to add extension sugar:
let arr = [1,2,3]
if let fourthItem = (3 < arr.count ? arr[3] : nil ) {
Swift.print("fourthItem: \(fourthItem)")
}else if let thirdItem = (2 < arr.count ? arr[2] : nil) {
Swift.print("thirdItem: \(thirdItem)")
}
//Output: thirdItem: 3
extension Array {
func isValidIndex(_ index : Int) -> Bool {
return index < self.count
}
}
let array = ["a","b","c","d"]
func testArrayIndex(_ index : Int) {
guard array.isValidIndex(index) else {
print("Handle array index Out of bounds here")
return
}
}
It's work for me to handle indexOutOfBounds.
Swift 4 and 5 extension:
As for me I think this is the safest solution:
public extension MutableCollection {
subscript(safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set(newValue) {
if let newValue = newValue, indices.contains(index) {
self[index] = newValue
}
}
}
}
Example:
let array = ["foo", "bar"]
if let str = array[safe: 1] {
print(str) // "bar"
} else {
print("index out of range")
}
I believe the existing answers could be improved further because this function could be needed in multiple places within a codebase (code smell when repeating common operations). So thought of adding my own implementation, with reasoning for why I considered this approach (efficiency is an important part of good API design, and should be preferred where possible as long as readability is not too badly affected). In addition to enforcing good Object-Oriented design with a method on the type itself, I think Protocol Extensions are great and we can make the existing answers even more Swifty. Limiting the extension is great because you don’t create code you don’t use. Making the code cleaner and extensible can often make maintenance easier, but there are trade-offs (succinctness being the one I thought of first).
So, you can note that if you'd ONLY like to use the extension idea for reusability but prefer the contains method referenced above you can rework this answer. I have tried to make this answer flexible for different uses.
TL;DR
You can use a more efficient algorithm (Space and Time) and make it extensible using a protocol extension with generic constraints:
extension Collection where Element: Numeric { // Constrain only to numerical collections i.e Int, CGFloat, Double and NSNumber
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
// Usage
let checkOne = digits.isIndexValid(index: index)
let checkTwo = [1,2,3].isIndexValid(index: 2)
Deep Dive
Efficiency
#Manuel's answer is indeed very elegant but it uses an additional layer of indirection (see here). The indices property acts like a CountableRange<Int> under the hood created from the startIndex and endIndex without reason for this problem (marginally higher Space Complexity, especially if the String is long). That being said, the Time Complexity should be around the same as a direct comparison between the endIndex and startIndex properties because N = 2 even though contains(_:) is O(N) for Collections (Ranges only have two properties for the start and end indices).
For the best Space and Time Complexity, more extensibility and only marginally longer code, I would recommend using the following:
extension Collection {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
Note here how I've used startIndex instead of 0 - this is to support ArraySlices and other SubSequence types. This was another motivation to post a solution.
Example usage:
let check = digits.isIndexValid(index: index)
For Collections in general, it's pretty hard to create an invalid Index by design in Swift because Apple has restricted the initializers for associatedtype Index on Collection - ones can only be created from an existing valid Collection.Index (like startIndex).
That being said, it's common to use raw Int indices for Arrays because there are many instances when you need to check random Array indices. So you may want to limit the method to fewer structs...
Limit Method Scope
You will notice that this solution works across all Collection types (extensibility), but you can restrict this to Arrays only if you want to limit the scope for your particular app (for example, if you don't want the added String method because you don't need it).
extension Array {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
For Arrays, you don't need to use an Index type explicitly:
let check = [1,2,3].isIndexValid(index: 2)
Feel free to adapt the code here for your own use cases, there are many types of other Collections e.g. LazyCollections. You can also use generic constraints, for example:
extension Collection where Element: Numeric {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
This limits the scope to Numeric Collections, but you can use String explicitly as well conversely. Again it's better to limit the function to what you specifically use to avoid code creep.
Referencing the method across different modules
The compiler already applies multiple optimizations to prevent generics from being a problem in general, but these don't apply when the code is being called from a separate module. For cases like that, using #inlinable can give you interesting performance boosts at the cost of an increased framework binary size. In general, if you're really into improving performance and want to encapsulate the function in a separate Xcode target for good SOC, you can try:
extension Collection where Element: Numeric {
// Add this signature to the public header of the extensions module as well.
#inlinable public func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}
I can recommend trying out a modular codebase structure, I think it helps to ensure Single Responsibility (and SOLID) in projects for common operations. We can try following the steps here and that is where we can use this optimisation (sparingly though). It's OK to use the attribute for this function because the compiler operation only adds one extra line of code per call site but it can improve performance further since a method is not added to the call stack (so doesn’t need to be tracked). This is useful if you need bleeding-edge speed, and you don’t mind small binary size increases. (-: Or maybe try out the new XCFrameworks (but be careful with the ObjC runtime compatibility for < iOS 13).
I think we should add this extension to every project in Swift
extension Collection {
#inlinable func isValid(position: Self.Index) -> Bool {
return (startIndex..<endIndex) ~= position
}
#inlinable func isValid(bounds: Range<Self.Index>) -> Bool {
return (startIndex..<endIndex) ~= bounds.upperBound
}
#inlinable subscript(safe position: Self.Index) -> Self.Element? {
guard isValid(position: position) else { return nil }
return self[position]
}
#inlinable subscript(safe bounds: Range<Self.Index>) -> Self.SubSequence? {
guard isValid(bounds: bounds) else { return nil }
return self[bounds]
}
}
extension MutableCollection {
#inlinable subscript(safe position: Self.Index) -> Self.Element? {
get {
guard isValid(position: position) else { return nil }
return self[position]
}
set {
guard isValid(position: position), let newValue = newValue else { return }
self[position] = newValue
}
}
#inlinable subscript(safe bounds: Range<Self.Index>) -> Self.SubSequence? {
get {
guard isValid(bounds: bounds) else { return nil }
return self[bounds]
}
set {
guard isValid(bounds: bounds), let newValue = newValue else { return }
self[bounds] = newValue
}
}
}
note that my isValid(position:) and isValid(bounds:) functions is of a complexity O(1), unlike most of the answers below, which uses the contains(_:) method which is of a complexity O(n)
Example usage:
let arr = ["a","b"]
print(arr[safe: 2] ?? "nil") // output: nil
print(arr[safe: 1..<2] ?? "nil") // output: nil
var arr2 = ["a", "b"]
arr2[safe: 2] = "c"
print(arr2[safe: 2] ?? "nil") // output: nil
arr2[safe: 1..<2] = ["c","d"]
print(arr[safe: 1..<2] ?? "nil") // output: nil