Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get next or previous item to an object in a Swift collection or array

Tags:

arrays

swift

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?

like image 982
Duncan Babbage Avatar asked Jul 27 '17 02:07

Duncan Babbage


People also ask

Which one is faster array or set in Swift?

Difference between an Array and Set in SwiftArray is faster than set in terms of initialization. Set is slower than an array in terms of initialization because it uses a hash process. The array allows storing duplicate elements in it. Set doesn't allow you to store duplicate elements in it.

What is .first in Swift?

first(where:) Returns the first element of the sequence that satisfies the given predicate.

Is array ordered in Swift?

Swift provides three primary collection types, known as arrays, sets, and dictionaries, for storing collections of values. Arrays are ordered collections of values. Sets are unordered collections of unique values.

How do arrays work in Swift?

An array is a list of values. In Swift, arrays are typed. You declare the type of the array (or Swift infers it) and you can only add items that are of the same type to the array. You can't generally store multiple different types in an array.


2 Answers

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.

like image 66
Duncan Babbage Avatar answered Oct 05 '22 13:10

Duncan Babbage


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.index(of: item)
            .flatMap(self.safeIndex(after:))
            .map{ self[$0] }
    }

    func item(afterWithWrapAround item: Element) -> Element? {
        return self.index(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.index(of: item)
            .flatMap(self.safeIndex(before:))
            .map{ self[$0] }
    }


    func item(beforeWithWrapAround item: Element) -> Element? {
        return self.index(of: item)
            .map(self.index(beforeWithWrapAround:))
            .map{ self[$0] }
    }
}
like image 43
Alexander Avatar answered Oct 05 '22 14:10

Alexander