How can I convert the function below to to swift 3? Currently getting a Binary operator '..<' cannot be applied to operands of type 'Int' and 'Self.IndexDistance' error.
extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in 0..<count - 1 { //error takes place here
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
reference: https://stackoverflow.com/a/24029847/5222077
count returns an IndexDistance which is the type describing
the distance between two collection indices. IndexDistance is
required to be a SignedInteger, but need not be an Int and can
be different from Index. Therefore it is not possible to create
the range 0..<count - 1.
A solution is to use startIndex and endIndex instead of 0 and count:
extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in startIndex ..< endIndex - 1 {
let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i
if i != j {
swap(&self[i], &self[j])
}
}
}
}
Another advantage is that this also works correctly with array slices
(where the index of the first element is not necessarily zero).
Note that according to the new "Swift API Design Guidelines",
shuffle() is the "proper" name for a mutating shuffle method,
and shuffled() for the non-mutating counterpart which returns an array:
extension Collection {
/// Return a copy of `self` with its elements shuffled
func shuffled() -> [Iterator.Element] {
var list = Array(self)
list.shuffle()
return list
}
}
Update: A (even more general) Swift 3 version has been added to
How do I shuffle an array in Swift? in the meantime.
For Swift 4 (Xcode 9) one has to replace the call to the swap()
function by a call to the swapAt() method of the collection.
Also the restriction on the Index type is no longer needed:
extension MutableCollection {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
for i in indices.dropLast() {
let diff = distance(from: i, to: endIndex)
let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(diff))))
swapAt(i, j)
}
}
}
See SE-0173 Add MutableCollection.swapAt(_:_:) for more information about swapAt.
As of Swift 4.2 (Xcode 10, currently in beta), with the implementation of
SE-0202 Random Unification,
shuffle() and shuffled() are part of the Swift standard library.
There is a fisher-yates shuffle in Gamekit:
import GameKit
let unshuffledArray = [1,2,3,4]
let shuffledArray = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: unshuffledArray)
print(shuffledArray)
You can also pass in and store a random seed, so you get the same sequence of pseudorandom shuffle values every time you supply the same seed in case you need to recreate a simulation.
import GameKit
let unshuffledArray = [1,2,3,4]
let randomSource = GKLinearCongruentialRandomSource(seed: 1)
let shuffledArray = randomSource.arrayByShufflingObjects(in: unshuffledArray)
//Always [1,4,2,3]
print(shuffledArray)
I would suggest simply shuffling arrays instead of trying to extend this to collections in general:
extension Array {
mutating func shuffle () {
for i in (0..<self.count).reversed() {
let ix1 = i
let ix2 = Int(arc4random_uniform(UInt32(i+1)))
(self[ix1], self[ix2]) = (self[ix2], self[ix1])
}
}
}
You can use the NSArray Extension from GameplayKit framework for this:
import GameplayKit
extension Collection {
func shuffled() -> [Iterator.Element] {
let shuffledArray = (self as? NSArray)?.shuffled()
let outputArray = shuffledArray as? [Iterator.Element]
return outputArray ?? []
}
mutating func shuffle() {
if let selfShuffled = self.shuffled() as? Self {
self = selfShuffled
}
}
}
// Usage example:
var numbers = [1,2,3,4,5]
numbers.shuffle()
print(numbers) // output example: [2, 3, 5, 4, 1]
print([10, "hi", 9.0].shuffled()) // output example: [hi, 10, 9]
Related
I have 2 Arrays. Say, array1 = [1,2,3,4,5] and array2 = [2,3]. How could I check in swift if array1 contains at least one item from array2?
You can do this by simply passing in your array2's contains function into your array1's contains function (or vice versa), as your elements are Equatable.
let array1 = [2, 3, 4, 5]
let array2 = [20, 15, 2, 7]
// this is just shorthand for array1.contains(where: { array2.contains($0) })
if array1.contains(where: array2.contains) {
print("Array 1 and array 2 share at least one common element")
} else {
print("Array 1 doesn't contains any elements from array 2")
}
This works by looping through array 1's elements. For each element, it will then loop through array 2 to check if it exists in that array. If it finds that element, it will break and return true – else false.
This works because there are actually two flavours of contains. One takes a closure in order to check each element against a custom predicate, and the other just compares an element directly. In this example, array1 is using the closure version, and array2 is using the element version. And that is the reason you can pass a contains function into another contains function.
Although, as correctly pointed out by #AMomchilov, the above algorithm is O(n2). A good set intersection algorithm is O(n), as element lookup is O(1). Therefore if your code is performance critical, you should definitely use sets to do this (if your elements are Hashable), as shown by #simpleBob.
Although if you want to take advantage of the early exit that contains gives you, you'll want to do something like this:
extension Sequence where Iterator.Element : Hashable {
func intersects<S : Sequence>(with sequence: S) -> Bool
where S.Iterator.Element == Iterator.Element
{
let sequenceSet = Set(sequence)
return self.contains(where: sequenceSet.contains)
}
}
if array1.intersects(with: array2) {
print("Array 1 and array 2 share at least one common element")
} else {
print("Array 1 doesn't contains any elements from array 2")
}
This works much the same as the using the array's contains method – with the significant difference of the fact that the arraySet.contains method is now O(1). Therefore the entire method will now run at O(n) (where n is the greater length of the two sequences), with the possibility of exiting early.
With Swift 5, you can use one of the following paths in order to find if two arrays have common elements or not.
#1. Using Set isDisjoint(with:) method
Set has a method called isDisjoint(with:). isDisjoint(with:) has the following declaration:
func isDisjoint(with other: Set<Element>) -> Bool
Returns a Boolean value that indicates whether the set has no members in common with the given sequence.
In order to test if two arrays have no common elements, you can use the Playground sample code below that implements isDisjoint(with:):
let array1 = [1, 3, 6, 18, 24]
let array2 = [50, 100, 200]
let hasNoCommonElement = Set(array1).isDisjoint(with: array2)
print(hasNoCommonElement) // prints: true
#2. Using Set intersection(_:) method
Set has a method called intersection(_:). intersection(_:) has the following declaration:
func intersection<S>(_ other: S) -> Set<Element> where Element == S.Element, S : Sequence
Returns a new set with the elements that are common to both this set and the given sequence.
In order to test if two arrays have no common elements or one or more common elements, you can use the Playground sample code below that implements intersection(_:):
let array1 = [1, 3, 6, 18, 24]
let array2 = [2, 3, 18]
let intersection = Set(array1).intersection(array2)
print(intersection) // prints: [18, 3]
let hasCommonElement = !intersection.isEmpty
print(hasCommonElement) // prints: true
An alternative way would be using Sets:
let array1 = [1,2,3,4,5]
let array2 = [2,3]
let set1 = Set(array1)
let intersect = set1.intersect(array2)
if !intersect.isEmpty {
// do something with the intersecting elements
}
Swift 5
Just make an extension
public extension Sequence where Element: Equatable {
func contains(anyOf sequence: [Element]) -> Bool {
return self.filter { sequence.contains($0) }.count > 0
}
}
Use:
let someArray = ["one", "two", "three"]
let string = "onE, Cat, dog"
let intersects = string
.lowercased()
.replacingOccurrences(of: " ", with: "")
.components(separatedBy: ",")
.contains(anyOf: someArray)
print(intersects) // true
let a1 = [1, 2, 3]
let a2 = [2, 3, 4]
Option 1
a2.filter { a1.contains($0) }.count > 1
Option 2
a2.reduce(false, combine: { $0 || a1.contains($1) })
Hope this helps.
//
// Array+CommonElements.swift
//
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
i have some question about swift 2 random. I have an enum sub class of all cards example:
enum CardName : Int{
case Card2Heart = 0,
Card2Diamond,
Card2Club,
Card2Spade,
Card3Heart..... }
I want to select 10 random cards on the didMoveToView
To get a unique, random set of numbers you can do the following...
Using the Fisher-Yates shuffle from here... How do I shuffle an array in Swift?
You can do...
var numbers = Array(0...51)
numbers.shuffleInPlace()
let uniqueSelection = numbers[0..<10]
or...
let uniqueSelection = Array(0...51).shuffleInPlace()[0..<10]
This will create a random, unique selection of 10 numbers (cards) from the array of 52 cards that you start with.
You can then iterate this array to get the enums or create an array of all enums to start from etc... There are lots of ways to use this.
In Swift 4.2 (coming with Xcode 10) the task will become much easier:
enum CardName: CaseIterable {
case Card2Heart
case Card2Diamond
case Card2Club
case Card2Spade
case Card3Heart
// ...
}
let randomCard = CardName.allCases.randomElement()
print(randomCard)
let randomCards10 = CardName.allCases.shuffled().prefix(10)
print(randomCards10)
Note there is no need for the enum to inherit from Int.
Following your last comment, here's a little, simplified example with the constraint of having to keep your enum for making the cards.
We need to include the extensions linked by Fogmeister:
extension MutableCollectionType where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in 0..<count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
extension CollectionType {
/// Return a copy of `self` with its elements shuffled
func shuffle() -> [Generator.Element] {
var list = Array(self)
list.shuffleInPlace()
return list
}
}
These extensions will allow us to shuffle an array of values.
Which array?
There's many ways, but the simplest option is probably to make an array of indices, which are simple integers (replace 52 with the actual number of cards in your enum):
Array(1...52) // [1, 2, 3, ... , 52]
We shuffle it:
Array(1...52).shuffle() // [33, 42, 7, ...]
Now we have an array of randomized indices. Let's make cards from this with your enum:
Array(0...51).shuffle().flatMap({ CardName(rawValue: $0) })
This is it, we have an array of cards in a random order:
let shuffledDeck = Array(0...51).shuffle().flatMap({ CardName(rawValue: $0) }) // [Card3Heart, Card2Diamond, ...]
and we can take cards from it:
func takeCardsFromDeck(number: Int) -> [CardName] {
if shuffledDeck.count > number {
let cards = Array(shuffledDeck[0..<number])
shuffledDeck.removeRange(0..<number)
return cards
}
return []
}
let tenRandomCards = takeCards(10)
Of course we need to remove from the deck the cards we've dealt, that way each card you draw is unique: we're using removeRange for that.
This example was kept simple on purpose: you still have to verify that there's enough cards in the deck before drawing, and lots of unsuspected other complexities. But it's so fun. ;)
If you want, you can search for additional inspiration in my implementation of these models and others (Deck, Dealer, Player, etc) in my PokerHands repository (MIT Licenced) on GitHub.
Swift 4.2
No need for these extensions anymore, we can use the .shuffle() and .shuffled() methods provided by Swift. Just remove the extensions, and rename the methods: the equivalent of our old "shuffleInPlace" is now .shuffle() and the equivalent of our old "shuffle" is now .shuffled().
Note: see Sulthan's answer for an even better solution using Swift 4.2.
Here is the shuffleInPlace() code that you are missing;
extension MutableCollectionType where Index == Int {
mutating func shuffleInPlace() {
if count < 2 { return }
for i in 0..<count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
how to randomly spread enum values set
import Darwin // arc4random_uniform
enum E:Int {
case E1, E2, E3, E4, E5, E6, E7, E8, E9, E10
static var set:[E] { return (E.E1.rawValue...E.E10.rawValue).flatMap { E(rawValue: $0) }}
}
func spread(i:Int = 0, arr:[E])->([E],[E]) {
var i = i == 0 ? arr.count : i
var e:[E] = []
var arr = arr
while i > 0 && arr.count > 0 {
let idx = Int(arc4random_uniform(UInt32(arr.count-1)))
e.append(arr.removeAtIndex(idx))
i -= 1
}
return (e,arr)
}
let e1 = spread(3, arr: E.set)
let e2 = spread(2, arr: e1.1)
// ... spread the rest
let e3 = spread(arr: e2.1)
print(e1, e2, e3, separator:"\n")
/*
([E.E8, E.E6, E.E4], [E.E1, E.E2, E.E3, E.E5, E.E7, E.E9, E.E10])
([E.E1, E.E7], [E.E2, E.E3, E.E5, E.E9, E.E10])
([E.E5, E.E3, E.E2, E.E9, E.E10], [])
*/
I have code which is basically like this:
func arrayHalvesEqual(data:[UInt8]) -> Bool {
let midPoint = data.count / 2
for i in 0..<midPoint {
let b = data[i]
let b2 = data[i + midPoint]
if b != b2 {
return false
}
}
return true
}
This works fine, but sometimes I want to pass in Arrays, and other times ArraySlice. I thought I'd change it to use generics and the CollectionType protocol, which converts as follows:
func arrayHalvesEqual<ByteArray : CollectionType where ByteArray.Generator.Element == UInt8>(data:ByteArray) -> Bool {
let midPoint = data.count / 2
for i in 0..<midPoint {
let b = data[i]
let b2 = data[i + midPoint]
if b != b2 {
return false
}
}
return true
}
However, I get the following compiler error:
error: binary operator '..<' cannot be applied to operands of type 'Int' and 'ByteArray.Index.Distance'
for i in 0..<midPoint {
I can switch the for loop to for i in data.indices which makes that compile, but then I can no longer divide it by 2 to get the midPoint, as data.indices returns the abstract CollectionType.Index whereas / 2 is an Int.
Is it possible to do something like this in Swift? Can I bridge between an abstract protocol Index type and some real type I can do maths on?
P.S: I've seen and found other examples for iterating over the whole collection by using indices and enumerate, but I explicitly only want to iterate over half the collection which requires some sort of division by 2
Thanks
You can restrict the method to collections which are indexed
by Int:
func arrayHalvesEqual<ByteArray : CollectionType where ByteArray.Index == Int, ByteArray.Generator.Element == UInt8>
(data:ByteArray) -> Bool { ... }
This covers both Array and ArraySlice.
And if you use indices.startIndex instead of 0 as initial index
then it suffices to restrict the index type to IntegerType.
Also the data type UInt8 can be replaced by a generic Equatable,
and the entire method shortened to
func arrayHalvesEqual<ByteArray : CollectionType where ByteArray.Index : IntegerType, ByteArray.SubSequence.Generator.Element : Equatable>
(data:ByteArray) -> Bool {
let midPoint = (data.indices.endIndex - data.indices.startIndex)/2
let firstHalf = data[data.indices.startIndex ..< midPoint]
let secondHalf = data[midPoint ..< data.indices.endIndex]
return !zip(firstHalf, secondHalf).contains { $0 != $1 }
}
let numbers = [1,3,4,5,5,9,0,1]
To find the first 5, use:
numbers.indexOf(5)
How do I find the second occurence?
List item
You can perform another search for the index of element at the remaining array slice as follow:
edit/update: Swift 5.2 or later
extension Collection where Element: Equatable {
/// Returns the second index where the specified value appears in the collection.
func secondIndex(of element: Element) -> Index? {
guard let index = firstIndex(of: element) else { return nil }
return self[self.index(after: index)...].firstIndex(of: element)
}
}
extension Collection {
/// Returns the second index in which an element of the collection satisfies the given predicate.
func secondIndex(where predicate: (Element) throws -> Bool) rethrows -> Index? {
guard let index = try firstIndex(where: predicate) else { return nil }
return try self[self.index(after: index)...].firstIndex(where: predicate)
}
}
Testing:
let numbers = [1,3,4,5,5,9,0,1]
if let index = numbers.secondIndex(of: 5) {
print(index) // "4\n"
} else {
print("not found")
}
if let index = numbers.secondIndex(where: { $0.isMultiple(of: 3) }) {
print(index) // "5\n"
} else {
print("not found")
}
Once you've found the first occurrence, you can use indexOf on the remaining slice of the array to locate the second occurrence:
let numbers = [1,3,4,5,5,9,0,1]
if let firstFive = numbers.indexOf(5) { // 3
let secondFive = numbers[firstFive+1..<numbers.count].indexOf(5) // 4
}
I don't think you can do it with indexOf. Instead you'll have to use a for-loop. A shorthand version:
let numbers = [1,3,4,5,5,9,0,1]
var indexes = [Int]()
numbers.enumerate().forEach { if $0.element == 5 { indexes += [$0.index] } }
print(indexes) // [3, 4]
Here's a general use extension of Array that will work for finding the nth element of a kind in any array:
extension Array where Element: Equatable {
// returns nil if there is no nth occurence
// or the index of the nth occurence if there is
func findNthIndexOf(n: Int, thing: Element) -> Int? {
guard n > 0 else { return nil }
var count = 0
for (index, item) in enumerate() where item == thing {
count += 1
if count == n {
return index
}
}
return nil
}
}
let numbers = [1,3,4,5,5,9,0]
numbers.findNthIndexOf(2, thing: 5) // returns 4
EDIT: as per #davecom's comment, I've included a similar but slightly more complex solution at the bottom of the answer.
I see a couple of good solutions here, especially considering the limitations the relatively new language of Swift. There is a really concise way to do it too, but beware...it is rather quick-and-dirty. May not be the perfect solution, but it is pretty quick. Also very versatile (not to brag).
extension Array where Element: Equatable {
func indexes(search: Element) -> [Int] {
return enumerate().reduce([Int]()) { $1.1 == search ? $0 + [$1.0] : $0 }
}
}
Using this extension, you could access the second index as follows:
let numbers = [1, 3, 4, 5, 5, 9, 0, 1]
let indexesOf5 = numbers.indexes(5) // [3, 4]
indexesOf5[1] // 4
And you're done!
Basically, the method works like this: enumerate() maps the array to tuples including the index of each element with the element itself. In this case, [1, 3, 4, 5, 5, 9, 0, 1].enumerate() returns a collection of the type EnumerateSequence<Array<Int>> which, translated to an Integer array, returns [(0,1), (1,3), (2,4), (3,5), (4,5), (5,9), (6,0), (7,1)].
The rest of the work is done using reduce (called 'inject' in some languages), which is an extremely powerful tool that many coders are not familiar with. If the reader is among those coders, I'd recommend checking out this article regarding use of the function in JS (keep in mind the placement of the non-block argument passed in is inputted after the block in JS, rather than before as seen here).
Thanks for reading.
P.S. not to be too long-winded on this relatively simple solution, but if the syntax for the indexes method shown above is a bit too quick-and-dirty, you could try something like this in the method body, where the closure's parameters are expanded for a bit more clarity:
return enumerate().reduce([Int]()) { memo, element in
element.1 == search ? memo + [element.0] : memo
}
EDIT: Here's another option that allows the implementer to scan for a specific "index at index" (e.g. the second occurrence of 5) for a more efficient solution.
extension Array where Element: Equatable {
func nIndex(search: Element, n: Int) -> Int? {
let info = enumerate().reduce((count: 0, index: 0), combine: { memo, element in
memo.count < n && element.1 == search ? (count: memo.count + 1, index: element.0) : memo
})
return info.count == n ? info.index : nil
}
}
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 2) // 4
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 3) // nil
The new method still iterates over the entire array, but is much more efficient due to the lack of "array-building" in the previous method. That performance hit would be negligible with the 8-object array used for the majority. But consider a list of 10,000 random numbers from 0 to 99:
let randomNumbers = (1...10000).map{_ in Int(rand() % 100)}
let indexes = randomNumbers.indexes(93) // count -> 100 (in my first run)
let index1 = indexes[1] // 238
// executed in 29.6603130102158 sec
let index2 = randomNumbers.nIndex(93, n: 2) // 238
// executed in 3.82625496387482 sec
As can be seen, this new method is considerably faster with the (very) large dataset; it is a bit more cumbersome and confusing though, so depending on your application, you may prefer the simpler solution, or a different one entirely.
(Again) thanks for reading.
extension Collection where Element: Equatable {
func nth(occurance: Int, of element: Element) -> Index? {
var level : Int = occurance
var position = self.startIndex
while let index = self[position...].index(of: element) {
level -= 1
guard level >= 0 else { return nil }
guard level != 0 else { return index }
position = self.index(after: index)
}
return nil
}
}
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