Last active
May 11, 2016 15:31
-
-
Save hooman/e77cc0e955a1db672ae49e45b0038d04 to your computer and use it in GitHub Desktop.
Proposed additional index functions for Swift Collections.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CollectionIndexExtensions.swift | |
// Swift3 | |
// | |
// Created by Hooman Mehr on 5/9/16. | |
// Copyright © 2016 Hooman Mehr. See the MIT license at the bottom. | |
// | |
/// Supply alternate API for index computations based on index offset from the begining of | |
/// the collection. This helps with converting code that heavily relies on C-array-like | |
/// integer indexes to work with custom index types. | |
extension Collection { | |
/// Returns the distance from the `startIndex` of the collection to the specified `index`. | |
/// | |
/// It helps convert a custom index type into `IndexDistance` which is an integer type | |
/// that supports mathematical operators for ease of index computations. | |
/// | |
/// | |
/// - Parameter index: An index that we want to know its offset from the start of the | |
/// collection. | |
/// - Returns: The offset (distance) of `index` from the start of the collection. | |
/// | |
/// - Precondition: `index >= self.startIndex && index <= self.endIndex` | |
/// | |
/// - SeeAlso: `index(atOffset:)`, `distance(from:to:)` | |
public func offset(of index: Index) -> IndexDistance { | |
return distance(from: startIndex, to: index) | |
} | |
/// Returns the index that is `at` the given `offset` from the `startIndex` of the | |
/// collection. | |
/// | |
/// This function compliments `offset()` to convert an `IndexDistance` back into | |
/// `Index`. The following example shows how it helps shorten some expressions: | |
/// | |
/// // Shortens this: | |
/// let i = collection.index(collection.startIndex, offsetBy: offset) | |
/// // Into this: | |
/// let i = collection.index(atOffset: offset) | |
/// | |
/// - Parameter offset: The distance of the desired index from the start of the | |
/// collection. | |
/// - Returns: The index that has the specified distance (offset) from the start of | |
/// the collection. | |
/// | |
/// - Precondition: `offset >= 0 && index <= self.distance(from:self.startIndex, to:self.endIndex)` | |
/// | |
/// - SeeAlso: `offset(_:)`, `index(_:,offsetBy:)`, `distance(from:to:)` | |
public func index(atOffset offset: IndexDistance) -> Index { | |
precondition(offset >= 0) | |
return index(startIndex, offsetBy: offset) | |
} | |
} | |
/// Supply additional `index(of:)` overloads adding the ability to skip | |
/// forward with `index(of:from:)` and finding the location of a sub-collection in the | |
/// collection. | |
/// | |
/// It works for collections where the element type is `Equatable` and the | |
/// associated `SubSequence` is also a `Collection` with the same element type. | |
extension Collection | |
where | |
Iterator.Element : Equatable, | |
SubSequence: Collection, | |
SubSequence.Iterator.Element == Iterator.Element | |
{ | |
/// Returns the first index `from` the specified `firstIndex` where the | |
/// specified value appears in the collection. | |
/// | |
/// After using `index(of:from:)` to find the position of a particular element | |
/// in a collection, you can use it to access the element by subscripting. | |
/// | |
/// Although the expression `suffix(from: firstIndex).index(of: element)` may | |
/// seem to do the same thing, the index it returns is only guaranteed to be | |
/// valid for the sub-sequence returned by `suffix(from:)`, not the parrent | |
/// collection. | |
/// | |
/// This example shows how you can find the number of occurrences of a name in | |
/// an array of students. | |
/// | |
/// let students = ["Amirali", "Ben", "Ben", "Bilal", "Cathy", "Dornaz"] | |
/// let name = "Ben" | |
/// | |
/// var count = 0, start = students.startIndex | |
/// while let i = students.index(of: name, from: start) { | |
/// count += 1 | |
/// start = students.index(after: i) | |
/// } | |
/// print("We have \(count) student(s) named \"\(name)\".") | |
/// // Prints "We have 2 student(s) named "Ben"." | |
/// | |
/// - Parameter element: An element to search for in the collection. | |
/// - Parameter firstIndex: An index from which to look for the `element`. | |
/// - Returns: The first index starting from `firstIndex` where `element` is | |
/// found. If `element` is not found in the collection, returns `nil`. | |
/// | |
/// - Precondition: `firstIndex >= self.startIndex && firstIndex <= self.endIndex` | |
/// | |
/// - SeeAlso: `index(from:where:)`, `index(of:)`, `index(where:)` | |
public func index(of element: Iterator.Element, from firstIndex: Index) -> Index? { | |
let subCollection = suffix(from: firstIndex) | |
guard let relativeIndex = subCollection.index(of: element) else { return nil } | |
return index(firstIndex, offsetBy: numericCast(subCollection.offset(of: relativeIndex))) | |
} | |
/// Returns the first index optionally starting `from` the specified `firstIndex` | |
/// where the specified sub-collection of values appear in the collection. | |
/// | |
/// After using `index(of:from:)` to find the starting position of a particular | |
/// sub-collection of elements in a collection, you can use it to access the elements | |
/// by subscripting and advancing. This example shows how you can find the occurrences | |
/// of a substring in a string using `characters` of a `String`. | |
/// | |
/// let sentence = "Sometimes it is, sometimes not; time will tell about this time." | |
/// let word = "time" | |
/// | |
/// let chars = sentence.characters | |
/// var wordStarts: [Int] = [], start = chars.startIndex | |
/// while let wordStart = chars.index(of: word.characters, from: start) { | |
/// wordStarts.append(chars.offset(of: wordStart)) | |
/// start = chars.index(after: wordStart) | |
/// } | |
/// print("The positions of '\(word)' in the sentence are: \(wordStarts)") | |
/// // Prints: "The positions of 'time' in the sentence are: [4, 21, 32, 58]" | |
/// | |
/// - Parameter elementSequence: A sequence of elements to search for in the | |
/// collection. | |
/// - Parameter firstIndex: An optional index from which to start looking for the | |
/// `elementSequence`. If you pass `nil`, `startIndex` will be used. The default | |
/// value is `nil`. | |
/// - Returns: The first index (starting from `firstIndex`) where `element` is found. | |
/// If `element` is not found in the collection, returns `nil`. | |
/// | |
/// - Precondition: `firstIndex >= startIndex && firstIndex <= endIndex` | |
/// | |
/// - SeeAlso: `index(of:)` | |
public func index< C: Collection where C.Iterator.Element == Iterator.Element> | |
(of elementSequence: C, from firstIndex: Index? = nil) -> Index? | |
{ | |
guard let firstElement = elementSequence.first | |
else { return firstIndex } // FIXME: First index matches empty `elementSequence`, or not? | |
var start = firstIndex ?? startIndex | |
while let theIndex = index(of: firstElement, from: start) { | |
if suffix(from: theIndex).starts(with: elementSequence) { return theIndex } | |
start = index(after: theIndex) | |
} | |
return nil // No match | |
} | |
} | |
/// Supply additional `index(where:)` variant adding the ability to skip | |
/// forward with `index(from:where:)`. | |
/// | |
/// It works for collections where the associated `SubSequence` is a | |
/// `Collection` with the same `Iterator.Element`, `Index` and | |
/// `IndexDistance` types. | |
extension Collection | |
where | |
SubSequence: Collection, | |
SubSequence.Iterator.Element == Iterator.Element | |
{ | |
/// Returns the first index `from` the specified `firstIndex` in which an | |
/// element of the collection satisfies the given predicate. | |
/// | |
/// You can use the predicate to find an element of a type that doesn't | |
/// conform to the `Equatable` protocol or to find an element that matches | |
/// particular criteria. Here's an example that finds the number of students | |
/// whose name begins with the letter "A": | |
/// | |
/// let students = ["Kofi", "Abena", "Peter", "Amirali", "Kweku", "Akosua"] | |
/// | |
/// var count = 0, start = students.startIndex | |
/// while let i = students.index(from: start, where: { $0.hasPrefix("A") }) { | |
/// count += 1 | |
/// start = students.index(after: i) | |
/// } | |
/// print("We have \(count) student(s) whose name start with 'A'.") | |
/// // Prints "We have 3 student(s) whose name start with 'A'." | |
/// | |
/// - Parameter firstIndex: An index from which to start testing the `predicate`. | |
/// - Parameter predicate: A closure that takes an element as its argument | |
/// and returns a Boolean value that indicates whether the passed element | |
/// represents a match. | |
/// - Returns: The index of the first element on or after `firstIndex` for which | |
/// `predicate` returns `true`. If no elements in the collection satisfy the | |
/// given predicate, returns `nil`. | |
/// | |
/// - Precondition: `firstIndex >= self.startIndex && firstIndex <= self.endIndex` | |
/// | |
/// - SeeAlso: `index(of:from:)` | |
public func index(from firstIndex: Index, where predicate: @noescape (Iterator.Element) throws -> Bool) rethrows -> Index? { | |
let subCollection = suffix(from: firstIndex) | |
guard let relativeIndex = try subCollection.index(where: predicate) else { return nil } | |
return index(firstIndex, offsetBy: numericCast(subCollection.offset(of: relativeIndex))) | |
} | |
} | |
// Copyright (c) 2016 Hooman Mehr ([email protected]) | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment