Skip to content

Instantly share code, notes, and snippets.

@hooman
Last active May 11, 2016 15:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hooman/e77cc0e955a1db672ae49e45b0038d04 to your computer and use it in GitHub Desktop.
Save hooman/e77cc0e955a1db672ae49e45b0038d04 to your computer and use it in GitHub Desktop.
Proposed additional index functions for Swift Collections.
//
// 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 (hooman@mac.com)
//
// 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