Related
I am trying to split a string into an array of letters, but keep some of the letters together. (I'm trying to break them into sound groups for pronunciation, for example).
So, for example, all the "sh' combinations would be one value in the array instead of two.
It is easy to find an 's' in an array that I know has an "sh" in it, using firstIndex. But how do I get more than just the first, or last, index of the array?
The Swift documentation includes this example:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
if let i = students.firstIndex(where: { $0.hasPrefix("A") }) {
print("\(students[i]) starts with 'A'!")
}
// Prints "Abena starts with 'A'!"
How do I get both Abena and Akosua (and others, if there were more?)
Here is my code that accomplishes some of what I want (please excuse the rather lame error catching)
let message = "she sells seashells"
var letterArray = message.map { String($0)}
var error = false
while error == false {
if message.contains("sh") {
guard let locate1 = letterArray.firstIndex(of: "s") else{
error = true
break }
let locate2 = locate1 + 1
//since it keeps finding an s it doesn't know how to move on to rest of string and we get an infinite loop
if letterArray[locate2] == "h"{
letterArray.insert("sh", at: locate1)
letterArray.remove (at: locate1 + 1)
letterArray.remove (at: locate2)}}
else { error = true }}
print (message, letterArray)
Instead of first use filter you will get both Abena and Akosua (and others, if there were more?)
extension Array where Element: Equatable {
func allIndexes(of element: Element) -> [Int] {
return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
}
}
You can then call
letterArray.allIndexes(of: "s") // [0, 4, 8, 10, 13, 18]
You can filter the collection indices:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let indices = students.indices.filter({students[$0].hasPrefix("A")})
print(indices) // "[1, 4]\n"
You can also create your own indices method that takes a predicate:
extension Collection {
func indices(where predicate: #escaping (Element) throws -> Bool) rethrows -> [Index] {
try indices.filter { try predicate(self[$0]) }
}
}
Usage:
let indices = students.indices { $0.hasPrefix("A") }
print(indices) // "[1, 4]\n"
or indices(of:) where the collection elements are Equatable:
extension Collection where Element: Equatable {
func indices(of element: Element) -> [Index] {
indices.filter { self[$0] == element }
}
}
usage:
let message = "she sells seashells"
let indices = message.indices(of: "s")
print(indices)
Note: If you need to find all ranges of a substring in a string you can check this post.
Have fun!
["Kofi", "Abena", "Peter", "Kweku", "Akosua"].forEach {
if $0.hasPrefix("A") {
print("\($0) starts with 'A'!")
}
}
If you really want to use the firstIndex method, here's a recursive(!) implementation just for fun :D
extension Collection where Element: Equatable {
/// Returns the indices of an element from the specified index to the end of the collection.
func indices(of element: Element, fromIndex: Index? = nil) -> [Index] {
let subsequence = suffix(from: fromIndex ?? startIndex)
if let elementIndex = subsequence.firstIndex(of: element) {
return [elementIndex] + indices(of: element, fromIndex: index(elementIndex, offsetBy: 1))
}
return []
}
}
Recursions
Given n instances of element in the collection, the function will be called n+1 times (including the first call).
Complexity
Looking at complexity, suffix(from:) is O(1), and firstIndex(of:) is O(n). Assuming that firstIndex terminates once it encounters the first match, any recursions simply pick up where we left off. Therefore, indices(of:fromIndex:) is O(n), just as good as using filter. Sadly, this function is not tail recursive... although we can change that by keeping a running total.
Performance
[Maybe I'll do this another time.]
Disclaimer
Recursion is fun and all, but you should probably use Leo Dabus' solution.
I have an array like:
var arr = [4,1,5,5,3]
I want to fetch subset from the array based on the occurrence of elements in it.
For example:
Elements with frequency 1 is {4,1,3}
Elements with frequency 2 is {5,5}
I followed this StackOverflow question but unable to figure out how to do the above thing.
Is there any way I can do this?
You can use an NSCountedSet to get the count of all elements in arr, then you can build a Dictionary, where the keys will be the number of occurencies for the elements and the values will be Arrays of the elements with key number of occurences. By iterating through Set(arr) rather than simply arr to build the Dictionary, you can make sure that repeating elements are only added once to the Dictionary (so for instance with your original example, 5 wouldn't be added twice as having a frequency of 2).
For the printing, you just need to iterate through the keys of the Dictionary and print the keys along with their corresponding values. I just sorted the keys to make the printing go in ascending order of number of occurences.
let arr = [4,1,5,5,3,2,3,6,2,7,8,2,7,2,8,8,8,7]
let counts = NSCountedSet(array: arr)
var countDict = [Int:[Int]]()
for element in Set(arr) {
countDict[counts.count(for: element), default: []].append(element)
}
countDict
for freq in countDict.keys.sorted() {
print("Elements with frequency \(freq) are {\(countDict[freq]!)}")
}
Output:
Elements with frequency 1 are {[4, 6, 1]}
Elements with frequency 2 are {[5, 3]}
Elements with frequency 3 are {[7]}
Elements with frequency 4 are {[2, 8]}
Swift 3 version:
let arr = [4,1,5,5,3,2,3,6,2,7,8,2,7,2,8,8,8,7]
let counts = NSCountedSet(array: arr)
var countDict = [Int:[Int]]()
for element in Set(arr) {
if countDict[counts.count(for: element)] != nil {
countDict[counts.count(for: element)]!.append(element)
} else {
countDict[counts.count(for: element)] = [element]
}
}
for freq in countDict.keys.sorted() {
print("Elements with frequency \(freq) are {\(countDict[freq]!)}")
}
You just need to get the occurrences of the elements and filter the elements that only occurs once or more than once as shown in this answer:
extension Array where Element: Hashable {
// Swift 4 or later
var occurrences: [Element: Int] {
return reduce(into: [:]) { $0[$1, default: 0] += 1 }
}
// // for Swift 3 or earlier
// var occurrences: [Element: Int] {
// var result: [Element: Int] = [:]
// forEach{ result[$0] = (result[$0] ?? 0) + 1}
// return result
// }
func frequencies(where isIncluded: (Int) -> Bool) -> Array {
return filter{ isIncluded(occurrences[$0] ?? 0) }
}
}
Playground Testing:
let arr = [5, 4, 1, 5, 5, 3, 5, 3]
let frequency1 = arr.frequencies {$0 == 1} // [4, 1]
let frequency2 = arr.frequencies {$0 == 2} // [3, 3]
let frequency3orMore = arr.frequencies {$0 >= 3} // [5, 5, 5, 5]
This is it:
func getSubset(of array: [Int], withFrequency frequency: Int) -> [Int]
{
var counts: [Int: Int] = [:]
for item in array
{
counts[item] = (counts[item] ?? 0) + 1
}
let filtered = counts.filter{ $0.value == frequency}
return Array(filtered.keys)
}
This is pure Swift (not using good old Next Step classes) and is using ideas from the SO link you supplied.
The counts dictionary contains the frequencies (value) of each of the int-values (key) in your array: [int-value : frequency].
var mentions = ["#alex", "#jason", "#jessica", "#john"]
I want to limit my array to 3 items, so I want to splice it:
var slice = [String]()
if mentions.count > 3 {
slice = mentions[0...3] //alex, jason, jessica
} else {
slice = mentions
}
However, I'm getting:
Ambiguous subscript with base type '[String]' and index type 'Range'
Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.31)
Target: x86_64-apple-macosx10.9
The problem is that mentions[0...3] returns an ArraySlice<String>, not an Array<String>. Therefore you could first use the Array(_:) initialiser in order to convert the slice into an array:
let first3Elements : [String] // An Array of up to the first 3 elements.
if mentions.count >= 3 {
first3Elements = Array(mentions[0 ..< 3])
} else {
first3Elements = mentions
}
Or if you want to use an ArraySlice (they are useful for intermediate computations, as they present a 'view' onto the original array, but are not designed for long term storage), you could subscript mentions with the full range of indices in your else:
let slice : ArraySlice<String> // An ArraySlice of up to the first 3 elements
if mentions.count >= 3 {
slice = mentions[0 ..< 3]
} else {
slice = mentions[mentions.indices] // in Swift 4: slice = mentions[...]
}
Although the simplest solution by far would be just to use the prefix(_:) method, which will return an ArraySlice of the first n elements, or a slice of the entire array if n exceeds the array count:
let slice = mentions.prefix(3) // ArraySlice of up to the first 3 elements
We can do like this,
let arr = [10,20,30,40,50]
let slicedArray = arr[1...3]
if you want to convert sliced array to normal array,
let arrayOfInts = Array(slicedArray)
You can try .prefix().
Returns a subsequence, up to the specified maximum length, containing the initial elements of the collection.
If the maximum length exceeds the number of elements in the collection, the result contains all the elements in the collection.
let numbers = [1, 2, 3, 4, 5]
print(numbers.prefix(2)) // Prints "[1, 2]"
print(numbers.prefix(10)) // Prints "[1, 2, 3, 4, 5]"
General solution:
extension Array {
func slice(size: Int) -> [[Element]] {
(0...(count / size)).map{Array(self[($0 * size)..<(Swift.min($0 * size + size, count))])}
}
}
Can also look at dropLast() function:
var mentions:[String] = ["#alex", "#jason", "#jessica", "#john"]
var slice:[String] = mentions
if mentions.count > 3 {
slice = Array(mentions.dropLast(mentions.count - 3))
}
//print(slice) => ["#alex", "#jason", "#jessica"]
I came up with this:
public extension Array {
func slice(count: Int) -> [some Collection] {
let n = self.count / count // quotient
let i = n * count // index
let r = self.count % count // remainder
let slices = (0..<n).map { $0 * count }.map { self[$0 ..< $0 + count] }
return (r > 0) ? slices + [self[i..<i + r]] : slices
}
}
You can also slice like this:
//Generic Method
func slice<T>(arrayList:[T], limit:Int) -> [T]{
return Array(arrayList[..<limit])
}
//How to Use
let firstThreeElements = slice(arrayList: ["#alex", "#jason", "#jessica", "#john"], limit: 3)
Array slice func extension:
extension Array {
func slice(with sliceSize: Int) -> [[Element]] {
guard self.count > 0 else { return [] }
var range = self.count / sliceSize
if self.count.isMultiple(of: sliceSize) {
range -= 1
}
return (0...range).map { Array(self[($0 * sliceSize)..<(Swift.min(($0 + 1) * sliceSize, self.count))]) }
}
}
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
}
}
If I have an array in Swift, and try to access an index that is out of bounds, there is an unsurprising runtime error:
var str = ["Apple", "Banana", "Coconut"]
str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION
However, I would have thought with all the optional chaining and safety that Swift brings, it would be trivial to do something like:
let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}
Instead of:
let theIndex = 3
if (theIndex < str.count) { // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}
But this is not the case - I have to use the ol' if statement to check and ensure the index is less than str.count.
I tried adding my own subscript() implementation, but I'm not sure how to pass the call to the original implementation, or to access the items (index-based) without using subscript notation:
extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
Alex's answer has good advice and solution for the question, however, I've happened to stumble on a nicer way of implementing this functionality:
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Example
let array = [1, 2, 3]
for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}
If you really want this behavior, it smells like you want a Dictionary instead of an Array. Dictionaries return nil when accessing missing keys, which makes sense because it's much harder to know if a key is present in a dictionary since those keys can be anything, where in an array the key must in a range of: 0 to count. And it's incredibly common to iterate over this range, where you can be absolutely sure have a real value on each iteration of a loop.
I think the reason it doesn't work this way is a design choice made by the Swift developers. Take your example:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"
If you already know the index exists, as you do in most cases where you use an array, this code is great. However, if accessing a subscript could possibly return nil then you have changed the return type of Array's subscript method to be an optional. This changes your code to:
var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
// ^ Added
Which means you would need to unwrap an optional every time you iterated through an array, or did anything else with a known index, just because rarely you might access an out of bounds index. The Swift designers opted for less unwrapping of optionals, at the expense of a runtime exception when accessing out of bounds indexes. And a crash is preferable to a logic error caused by a nil you didn't expect in your data somewhere.
And I agree with them. So you won't be changing the default Array implementation because you would break all the code that expects a non-optional values from arrays.
Instead, you could subclass Array, and override subscript to return an optional. Or, more practically, you could extend Array with a non-subscript method that does this.
extension Array {
// Safely lookup an index that might be out of bounds,
// returning nil if it does not exist
func get(index: Int) -> T? {
if 0 <= index && index < count {
return self[index]
} else {
return nil
}
}
}
var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
print("I ate a \( fruit )")
// I ate a Banana
}
if let fruit = fruits.get(3) {
print("I ate a \( fruit )")
// never runs, get returned nil
}
Swift 3 Update
func get(index: Int) ->T? needs to be replaced by func get(index: Int) ->Element?
To build on Nikita Kukushkin's answer, sometimes you need to safely assign to array indexes as well as read from them, i.e.
myArray[safe: badIndex] = newValue
So here is an update to Nikita's answer (Swift 3.2) that also allows safely writing to mutable array indexes, by adding the safe: parameter name.
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
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
}
}
}
}
extension Array {
subscript (safe index: Index) -> Element? {
0 <= index && index < count ? self[index] : nil
}
}
O(1) performance
type safe
correctly deals with Optionals for [MyType?] (returns MyType??, that can be unwrapped on both levels)
does not lead to problems for Sets
concise code
Here are some tests I ran for you:
let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
Swift 4
An extension for those who prefer a more traditional syntax:
extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Valid in Swift 2
Even though this has been answered plenty of times already, I'd like to present an answer more in line in where the fashion of Swift programming is going, which in Crusty's words¹ is: "Think protocols first"
• What do we want to do?
- Get an Element of an Array given an Index only when it's safe, and nil otherwise
• What should this functionality base it's implementation on?
- Array subscripting
• Where does it get this feature from?
- Its definition of struct Array in the Swift module has it
• Nothing more generic/abstract?
- It adopts protocol CollectionType which ensures it as well
• Nothing more generic/abstract?
- It adopts protocol Indexable as well...
• Yup, sounds like the best we can do. Can we then extend it to have this feature we want?
- But we have very limited types (no Int) and properties (no count) to work with now!
• It will be enough. Swift's stdlib is done pretty well ;)
extension Indexable {
public subscript(safe safeIndex: Index) -> _Element? {
return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
}
}
¹: not true, but it gives the idea
Because arrays may store nil values, it does not make sense to return a nil if an array[index] call is out of bounds.
Because we do not know how a user would like to handle out of bounds problems, it does not make sense to use custom operators.
In contrast, use traditional control flow for unwrapping objects and ensure type safety.
if let index = array.checkIndexForSafety(index:Int)
let item = array[safeIndex: index]
if let index = array.checkIndexForSafety(index:Int)
array[safeIndex: safeIndex] = myObject
extension Array {
#warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {
if indices.contains(index) {
// wrap index number in object, so can ensure type safety
return SafeIndex(indexNumber: index)
} else {
return nil
}
}
subscript(index:SafeIndex) -> Element {
get {
return self[index.indexNumber]
}
set {
self[index.indexNumber] = newValue
}
}
// second version of same subscript, but with different method signature, allowing user to highlight using safe index
subscript(safeIndex index:SafeIndex) -> Element {
get {
return self[index.indexNumber]
}
set {
self[index.indexNumber] = newValue
}
}
}
public class SafeIndex {
var indexNumber:Int
init(indexNumber:Int){
self.indexNumber = indexNumber
}
}
I realize this is an old question. I'm using Swift5.1 at this point, the OP was for Swift 1 or 2?
I needed something like this today, but I didn't want to add a full scale extension for just the one place and wanted something more functional (more thread safe?). I also didn't need to protect against negative indices, just those that might be past the end of an array:
let fruit = ["Apple", "Banana", "Coconut"]
let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil
For those arguing about Sequences with nil's, what do you do about the first and last properties that return nil for empty collections?
I liked this because I could just grab at existing stuff and use it to get the result I wanted. I also know that dropFirst(n) is not a whole collection copy, just a slice. And then the already existent behavior of first takes over for me.
I found safe array get, set, insert, remove very useful. I prefer to log and ignore the errors as all else soon gets hard to manage. Full code bellow
/**
Safe array get, set, insert and delete.
All action that would cause an error are ignored.
*/
extension Array {
/**
Removes element at index.
Action that would cause an error are ignored.
*/
mutating func remove(safeAt index: Index) {
guard index >= 0 && index < count else {
print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
return
}
remove(at: index)
}
/**
Inserts element at index.
Action that would cause an error are ignored.
*/
mutating func insert(_ element: Element, safeAt index: Index) {
guard index >= 0 && index <= count else {
print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
return
}
insert(element, at: index)
}
/**
Safe get set subscript.
Action that would cause an error are ignored.
*/
subscript (safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set {
remove(safeAt: index)
if let element = newValue {
insert(element, safeAt: index)
}
}
}
}
Tests
import XCTest
class SafeArrayTest: XCTestCase {
func testRemove_Successful() {
var array = [1, 2, 3]
array.remove(safeAt: 1)
XCTAssert(array == [1, 3])
}
func testRemove_Failure() {
var array = [1, 2, 3]
array.remove(safeAt: 3)
XCTAssert(array == [1, 2, 3])
}
func testInsert_Successful() {
var array = [1, 2, 3]
array.insert(4, safeAt: 1)
XCTAssert(array == [1, 4, 2, 3])
}
func testInsert_Successful_AtEnd() {
var array = [1, 2, 3]
array.insert(4, safeAt: 3)
XCTAssert(array == [1, 2, 3, 4])
}
func testInsert_Failure() {
var array = [1, 2, 3]
array.insert(4, safeAt: 5)
XCTAssert(array == [1, 2, 3])
}
func testGet_Successful() {
var array = [1, 2, 3]
let element = array[safe: 1]
XCTAssert(element == 2)
}
func testGet_Failure() {
var array = [1, 2, 3]
let element = array[safe: 4]
XCTAssert(element == nil)
}
func testSet_Successful() {
var array = [1, 2, 3]
array[safe: 1] = 4
XCTAssert(array == [1, 4, 3])
}
func testSet_Successful_AtEnd() {
var array = [1, 2, 3]
array[safe: 3] = 4
XCTAssert(array == [1, 2, 3, 4])
}
func testSet_Failure() {
var array = [1, 2, 3]
array[safe: 4] = 4
XCTAssert(array == [1, 2, 3])
}
}
Swift 5.x
An extension on RandomAccessCollection means that this can also work for ArraySlice from a single implementation. We use startIndex and endIndex as array slices use the indexes from the underlying parent Array.
public extension RandomAccessCollection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
/// - complexity: O(1)
subscript (safe index: Index) -> Element? {
guard index >= startIndex, index < endIndex else {
return nil
}
return self[index]
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}
Using Above mention extension return nil if anytime index goes out of bound.
let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")
result - nil
I have padded the array with nils in my use case:
let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]
switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
// process last component with 5
default:
break
}
Also check the subscript extension with safe: label by Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/
The "Commonly Rejected Changes" for Swift list contains a mention of changing Array subscript access to return an optional rather than crashing:
Make Array<T> subscript access return T? or T! instead of T: The current array behavior is intentional, as it accurately reflects the fact that out-of-bounds array access is a logic error. Changing the current behavior would slow Array accesses to an unacceptable degree. This topic has come up multiple times before but is very unlikely to be accepted.
https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types
So the basic subscript access will not be changing to return an optional.
However, the Swift team/community does seem open to adding a new optional-returning access pattern to Arrays, either via a function or subscript.
This has been proposed and discussed on the Swift Evolution forum here:
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871
Notably, Chris Lattner gave the idea a "+1":
Agreed, the most frequently suggested spelling for this is: yourArray[safe: idx], which seems great to me. I am very +1 for adding this.
https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13
So this may be possible out of the box in some future version of Swift. I'd encourage anyone who wants it to contribute to that Swift Evolution thread.
Not sure why no one, has put up an extension that also has a setter to automatically grow the array
extension Array where Element: ExpressibleByNilLiteral {
public subscript(safe index: Int) -> Element? {
get {
guard index >= 0, index < endIndex else {
return nil
}
return self[index]
}
set(newValue) {
if index >= endIndex {
self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
}
self[index] = newValue ?? nil
}
}
}
Usage is easy and works as of Swift 5.1
var arr:[String?] = ["A","B","C"]
print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]
arr[safe:10] = "Z"
print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]
Note: You should understand the performance cost (both in time/space) when growing an array in swift - but for small problems sometimes you just need to get Swift to stop Swifting itself in the foot
To propagate why operations fail, errors are better than optionals.
public extension Collection {
/// Ensure an index is valid before accessing an element of the collection.
/// - Returns: The same as the unlabeled subscript, if an error is not thrown.
/// - Throws: `AnyCollection<Element>.IndexingError`
/// if `indices` does not contain `index`.
subscript(validating index: Index) -> Element {
get throws {
guard indices.contains(index)
else { throw AnyCollection<Element>.IndexingError() }
return self[index]
}
}
}
public extension AnyCollection {
/// Thrown when `[validating:]` is called with an invalid index.
struct IndexingError: Error { }
}
XCTAssertThrowsError(try ["🐾", "🥝"][validating: 2])
let collection = Array(1...10)
XCTAssertEqual(try collection[validating: 0], 1)
XCTAssertThrowsError(try collection[validating: collection.endIndex]) {
XCTAssert($0 is AnyCollection<Int>.IndexingError)
}
I think this is not a good idea. It seems preferable to build solid code that does not result in trying to apply out-of-bounds indexes.
Please consider that having such an error fail silently (as suggested by your code above) by returning nil is prone to producing even more complex, more intractable errors.
You could do your override in a similar fashion you used and just write the subscripts in your own way. Only drawback is that existing code will not be compatible. I think to find a hook to override the generic x[i] (also without a text preprocessor as in C) will be challenging.
The closest I can think of is
// compile error:
if theIndex < str.count && let existing = str[theIndex]
EDIT: This actually works. One-liner!!
func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
return idx < array.count ? array[idx] : nil
}
if let x: AnyObject = ifInBounds(swiftarray, 3) {
println(x)
}
else {
println("Out of bounds")
}
I have made a simple extension for array
extension Array where Iterator.Element : AnyObject {
func iof (_ i : Int ) -> Iterator.Element? {
if self.count > i {
return self[i] as Iterator.Element
}
else {
return nil
}
}
}
it works perfectly as designed
Example
if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode,
You can try
if index >= 0 && index < array.count {
print(array[index])
}
To be honest I faced this issue too. And from performance point of view a Swift array should be able to throw.
let x = try a[y]
This would be nice and understandable.
When you only need to get values from an array and you don't mind a small performance penalty (i.e. if your collection isn't huge), there is a Dictionary-based alternative that doesn't involve (a too generic, for my taste) collection extension:
// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
2022
infinite index access and safe idx access(returns nil in case no such idex):
public extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
subscript (infinityIdx idx: Index) -> Element where Index == Int {
return self[ abs(idx) % self.count ]
}
}
but be careful, it will throw an exception in case of array/collection is empty
usage
(0...10)[safe: 11] // nil
(0...10)[infinityIdx: 11] // 0
(0...10)[infinityIdx: 12] // 1
(0...10)[infinityIdx: 21] // 0
(0...10)[infinityIdx: 22] // 1
Swift 5 Usage
extension WKNavigationType {
var name : String {
get {
let names = ["linkAct","formSubm","backForw","reload","formRelo"]
return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
}
}
}
ended up with but really wanted to do generally like
[<collection>][<index>] ?? <default>
but as the collection is contextual I guess it's proper.