Reputation: 20187
What is the best way to identify the item that precedes or follows a specified object in a Swift array, while protecting against out of bounds errors?
Upvotes: 27
Views: 12932
Reputation: 30727
Here is an alternative for those that don't like working with indicies:
extension Collection where Iterator.Element: Equatable {
typealias Element = Self.Iterator.Element
func after(_ item: Element) -> Element? {
var after: Element?
var reverse = reversed().makeIterator()
while case let next = reverse.next(), next != item {
after = next
}
return after
}
}
It iterates backwards, keeping track of the current item and breaking when it sees the item you wanted the one next of.
Upvotes: 0
Reputation: 1
import UIKit
extension Array where Element : Equatable {
/// Finds next element of the array, returns nil if there is no next element
func nextElement(element: Element, completion: @escaping(Element?)->()){
let currentIndex = self.firstIndex { $0 == element}
if let currIndex = currentIndex?.advanced(by: 1) {
completion(currIndex >= self.endIndex ? nil : self[currIndex])
}else{
completion(nil)
}
}
/// Finds prev element of the array, returns nil if there is no previous element
func prevElement(element: Element, completion: @escaping(Element?)->()){
let currentIndex = self.firstIndex { $0 == element}
if let currIndex = currentIndex?.advanced(by: -1) {
completion(currIndex <= self.endIndex ? nil : self[currIndex])
}else{
completion(nil)
}
}
}
Upvotes: -1
Reputation: 63359
I propose a simpler and more thorough implementation:
extension Collection where Iterator.Element: Equatable {
typealias Element = Self.Iterator.Element
func safeIndex(after index: Index) -> Index? {
let nextIndex = self.index(after: index)
return (nextIndex < self.endIndex) ? nextIndex : nil
}
func index(afterWithWrapAround index: Index) -> Index {
return self.safeIndex(after: index) ?? self.startIndex
}
func item(after item: Element) -> Element? {
return self.firstIndex(of: item)
.flatMap(self.safeIndex(after:))
.map{ self[$0] }
}
func item(afterWithWrapAround item: Element) -> Element? {
return self.firstIndex(of: item)
.map(self.index(afterWithWrapAround:))
.map{ self[$0] }
}
}
extension BidirectionalCollection where Iterator.Element: Equatable {
typealias Element = Self.Iterator.Element
func safeIndex(before index: Index) -> Index? {
let previousIndex = self.index(before: index)
return (self.startIndex <= previousIndex) ? previousIndex : nil
}
func index(beforeWithWrapAround index: Index) -> Index {
return self.safeIndex(before: index) ?? self.index(before: self.endIndex)
}
func item(before item: Element) -> Element? {
return self.firstIndex(of: item)
.flatMap(self.safeIndex(before:))
.map{ self[$0] }
}
func item(beforeWithWrapAround item: Element) -> Element? {
return self.firstIndex(of: item)
.map(self.index(beforeWithWrapAround:))
.map{ self[$0] }
}
}
Upvotes: 19
Reputation: 1044
Why not use Swift's factory methods?
try this to get a previous item to an object.
let currentIndex = yourArr.index(of: "0")
let previousVal = yourArr[currentIndex-1]
First get the index of object you want previous item for, then get the item of that index-1.
Upvotes: -1
Reputation: 20187
A good way to approach this is with an extension on the Swift Array, or in this case a more generalised solution for all BidirectionalCollection objects, including arrays.
The following provides methods for getting the next or previous object after your specified object from an array, with an optional parameter if you want the function to loop at the ends of the array.
These functions return nil if the original object is not present in the Array, and also if you ask for the previous item to the first object or the item that follows the last object, for the non-looping functions.
//
// Array+Iterator.swift
//
extension BidirectionalCollection where Iterator.Element: Equatable {
typealias Element = Self.Iterator.Element
func after(_ item: Element, loop: Bool = false) -> Element? {
if let itemIndex = self.index(of: item) {
let lastItem: Bool = (index(after:itemIndex) == endIndex)
if loop && lastItem {
return self.first
} else if lastItem {
return nil
} else {
return self[index(after:itemIndex)]
}
}
return nil
}
func before(_ item: Element, loop: Bool = false) -> Element? {
if let itemIndex = self.index(of: item) {
let firstItem: Bool = (itemIndex == startIndex)
if loop && firstItem {
return self.last
} else if firstItem {
return nil
} else {
return self[index(before:itemIndex)]
}
}
return nil
}
}
Usage: If you have an array of your children, and want to know the child that comes after Jane, you would use the following:
let nextChild = children.after(jane)
If you simply want to know whose turn it is to do the dishes, and Sammy did them last night, you'd instead use:
let dishwasherTonight = children.after(sammy, loop: true)
That way, if Sammy is the youngest child, his oldest sibling will be assigned to wash the dishes tonight as we loop back to the start of the array.
Postscript: note re the comparison to endIndex in the code the definition of that property:
You can access an element of a collection through its subscript by using any valid index except the collection’s endIndex property. This property is a “past the end” index that does not correspond with any element of the collection.
Upvotes: 34