Skip to content

Instantly share code, notes, and snippets.

@milseman
Last active May 3, 2019 00:21
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 milseman/1f4b998cad821d6e6853a7567c3e099d to your computer and use it in GitHub Desktop.
Save milseman/1f4b998cad821d6e6853a7567c3e099d to your computer and use it in GitHub Desktop.
Offset Indexing, using phantom-typed OffsetRange
// TODO: doc
public struct OffsetBound<Bound> {
internal enum Kind {
case fromStart(Int)
case fromEnd(Int)
}
internal var kind: Kind
public init(fromStart: Int) {
self.kind = .fromStart(fromStart)
}
public init(fromEnd: Int) {
self.kind = .fromEnd(fromEnd)
}
}
extension OffsetBound {
// TODO: doc
public func advanced(by: Int) -> OffsetBound {
switch self.kind {
case .fromStart(let offset): return OffsetBound(fromStart: offset + by)
case .fromEnd(let offset): return OffsetBound(fromEnd: offset + by)
}
}
// TODO: doc
public func relative<C: Collection>(to c: C) -> C.Index {
switch self.kind {
case .fromStart(let int):
if int < 0 { return c.startIndex }
return c.index(
c.startIndex, offsetBy: int, limitedBy: c.endIndex
) ?? c.endIndex
case .fromEnd(let int):
if int > 0 { return c.endIndex }
return c.index(
c.endIndex, offsetBy: int, limitedBy: c.startIndex
) ?? c.startIndex
}
}
}
extension OffsetBound: ExpressibleByIntegerLiteral {
// TODO: doc
public init(integerLiteral int: Int) {
if int < 0 { self.init(fromEnd: int) } else { self.init(fromStart: int) }
}
}
// TODO: doc
public struct OffsetRange<Bound> {
internal enum Kind {
case full(lower: OffsetBound<Bound>, upper: OffsetBound<Bound>)
case partialFrom(lower: OffsetBound<Bound>)
case partialTo(upper: OffsetBound<Bound>)
}
internal var kind: Kind
// lower ..< upper
init(lower: OffsetBound<Bound>, upper: OffsetBound<Bound>) {
self.kind = .full(lower: lower, upper: upper)
}
// lower ... upper
init(lower: OffsetBound<Bound>, through: OffsetBound<Bound>) {
self.init(lower: lower, upper: through.advanced(by: 1))
}
// from ...
init(partialFrom from: OffsetBound<Bound>) {
self.kind = .partialFrom(lower: from)
}
// ..< to
init(partialTo to: OffsetBound<Bound>) {
self.kind = .partialTo(upper: to)
}
}
extension OffsetRange {
// TODO: doc
public func relative<C: Collection>(
to c: C
) -> Range<C.Index> where Bound == C.Index {
switch self.kind {
case .full(let lower, let upper):
let lb = lower.relative(to: c)
return lb ..< max(lb, upper.relative(to: c))
case .partialFrom(let lower):
return lower.relative(to: c) ..< c.endIndex
case .partialTo(let upper):
return c.startIndex ..< upper.relative(to: c)
}
}
}
// TODO: Throw in the intersection of existing conformances on range types
extension OffsetBound {
// TODO: doc
public static func ..< (
lhs: OffsetBound, rhs: OffsetBound
) -> OffsetRange<Bound> {
return OffsetRange(lower: lhs, upper: rhs)
}
// TODO: doc
public static func ... (
lhs: OffsetBound, rhs: OffsetBound
) -> OffsetRange<Bound> {
return OffsetRange(lower: lhs, upper: rhs.advanced(by: 1))
}
// TODO: doc
public static prefix func ..< (
maximum: OffsetBound
) -> OffsetRange<Bound> {
return OffsetRange(partialTo: maximum)
}
// TODO: doc
public static prefix func ... (
maximum: OffsetBound
) -> OffsetRange<Bound> {
return OffsetRange(partialTo: maximum.advanced(by: 1))
}
// TODO: doc
public static postfix func ... (
minimum: OffsetBound
) -> OffsetRange<Bound> {
return OffsetRange(partialFrom: minimum)
}
}
// TODO: doc
public protocol IndexRangeExpression {
associatedtype Bound
func relative<C: Collection>(
to: C
) -> Range<C.Index> where Bound == C.Index
}
extension OffsetRange: IndexRangeExpression {
}
extension Collection {
// TODO: doc
public subscript(offset offset: OffsetBound<Index>) -> Element {
return self[offset.relative(to: self)]
}
public subscript<IRE: IndexRangeExpression>(
offset range: IRE
) -> SubSequence where IRE.Bound == Index {
return self[range.relative(to: self)]
}
}
extension MutableCollection {
// TODO: doc
public subscript(offset: OffsetBound<Index>) -> Element {
get {
return self[offset.relative(to: self)]
}
set {
self[offset.relative(to: self)] = newValue
}
}
public subscript<IRE: IndexRangeExpression>(
offset range: IRE
) -> SubSequence where IRE.Bound == Index {
get {
return self[range.relative(to: self)]
}
set {
self[range.relative(to: self)] = newValue
}
}
}
// Examples
func printStrings() {
let str = "abcdefghijklmnopqrstuvwxyz"
let idx = str.firstIndex { $0 == "n" }!
print("-- single element subscript --")
print(str[offset: -4]) // w
print(str[..<idx][offset: -100]) // a
print(str[idx...][offset: 1]) // o
// No equivalent: print(str[(idx++1)--10]) // e
print("-- relative range --")
print(str[offset: 1 ..< -2]) // bcdefghijklmnopqrstuvwx
// No equivalent: print(str[..<idx--2 ..< --2]) // lmnopqrstuvwx
print(str[idx...][offset: ..<(-2)]) // nopqrstuvwx
print(str[..<idx][offset: (-2)...]) // lm
// No equivalent: print(str[idx--2..<idx++3]) // lmnop
print(str[offset: -4 ..< -2]) // wx
print("-- relative range through --")
// No equivalent: print(str[idx--2 ... --2]) // lmnopqrstuvwxy
print(str[idx...][offset: ...(-2)]) // nopqrstuvwxy
print(str[...idx][offset: (-3)...]) // lmn
// No equivalent: print(str[idx--2...idx++3]) // lmnopq
print(str[offset: -4 ... -2]) // wxy
print("-- partial relative range up to --")
// No equivalent: print(str[..<idx++2]) // abcdefghijklmno
print(str[..<idx][offset: ..<(-2)]) // abcdefghijk
print(str[offset: ..<20]) // abcdefghijklmnopqrst
print(str[offset: ..<(-20)]) // abcdef
print("-- partial relative range through --")
// No equivalent: print(str[...idx++2]) // abcdefghijklmnop
print(str[...idx][offset: ...(-3)]) // abcdefghijkl
print(str[offset: ...20]) // abcdefghijklmnopqrstu
print(str[offset: ...(-20)]) // abcdefg
print(str[offset: ...(-20)][offset: ...(-3)]) // abcde
// No equivalent: print(str[...((--20)++2)]) // abcdefghi
print("-- partial relative range from --")
print(str[idx...][offset: 2...]) // pqrstuvwxyz
// No equivalent: print(str[idx--2...]) // lmnopqrstuvwxyz
print(str[offset: 20...]) // uvwxyz
print(str[offset: (-20)...]) // ghijklmnopqrstuvwxyz
}
func printSplitFloats() {
func splitAndTruncate<T: BinaryFloatingPoint>(
_ value: T, precision: Int = 3
) -> (whole: Substring, fraction: Substring) {
let str = String(describing: value)
guard let dotIdx = str.firstIndex(of: ".") else { return (str[...], "") }
return (str[..<dotIdx], str[dotIdx...][offset: 1..<OffsetBound(1).advanced(by: precision)])
}
print(splitAndTruncate(1.0)) // (whole: "1", fraction: "0")
print(splitAndTruncate(1.25)) // (whole: "1", fraction: "25")
print(splitAndTruncate(1.1000000000000001)) // (whole: "1", fraction: "1")
print(splitAndTruncate(1.3333333)) // (whole: "1", fraction: "333")
print(splitAndTruncate(200)) // (whole: "200", fraction: "0")
}
func printRanges() {
let r: Range<Int> = 3..<10 // Explicit type only needed for this gist, if part of stdlib, it won't be preferred...
print((absolute: r[5...], relative: r[offset: 5...]))
// (absolute: Range(5..<10), relative: Range(8..<10))
}
func printFifths() {
func getFifth<C: RandomAccessCollection>(
_ c: C
) -> (absolute: C.Element, relative: C.Element) where C.Index == Int {
return (c[5], c[offset: 5])
}
let array = [0, 1,2,3,4,5,6,7,8,9]
print(getFifth(array)) // (absolute: 5, relative: 5)
print(getFifth(array[2...])) // (absolute: 5, relative: 7)
}
import Foundation
func printDataFifths() {
func getFifth(_ data: Data) -> (absolute: UInt8, relative: UInt8) {
return (data[5], data[offset: 5])
}
var data = Data([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(getFifth(data)) // (absolute: 5, relative: 5)
data = data.dropFirst()
print(getFifth(data)) // (absolute: 5, relative: 6)
}
func printRequirements() {
func parseRequirement(
_ str: Substring
) -> (predecessor: Unicode.Scalar, successor: Unicode.Scalar) {
return (str.unicodeScalars[offset: 5], str.unicodeScalars[offset: 36])
}
"""
Step C must be finished before step A can begin.
Step C must be finished before step F can begin.
Step A must be finished before step B can begin.
Step A must be finished before step D can begin.
Step B must be finished before step E can begin.
Step D must be finished before step E can begin.
Step F must be finished before step E can begin.
""".split(separator: "\n").forEach { print(parseRequirement($0)) }
// (predecessor: "C", successor: "A")
// (predecessor: "C", successor: "F")
// (predecessor: "A", successor: "B")
// (predecessor: "A", successor: "D")
// (predecessor: "B", successor: "E")
// (predecessor: "D", successor: "E")
// (predecessor: "F", successor: "E")
}
func runAll() {
printStrings()
printSplitFloats()
printRanges()
printFifths()
printDataFifths()
printRequirements()
}
runAll()
let intRange = 1..<5
dump(intRange) // Range<Int>
let range: OffsetRange<String.Index> = 1...(-1)
let x = " Hello, World! "
let y = "abcdefg"
let z = " "
print(x[offset: range]) // "Hello, World!"
print(y[offset: range]) // "bcdef"
print(z[offset: range]) // ""
print("abc"[offset: 1...]) // bc
print("abc"[offset: 1..<(-1)]) // b
print("abc"[offset: 1..<(-2)]) // ""
print("abc"[offset: 1..<(-3)]) // ""
extension BidirectionalCollection {
func element(beforeOffset offset: OffsetBound<Index>) -> Element {
return self[offset: offset.advanced(by: -1)]
}
func element(beforeOffset offset: Int) -> Element {
return self[offset: OffsetBound(fromStart: offset - 1)]
}
}
let array = ["a", "b", "c", "d"]
print(array.element(beforeOffset: OffsetBound(0))) // "a"
print(array.element(beforeOffset: 0)) // "a"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment