Skip to content

Instantly share code, notes, and snippets.

@milseman
Created May 6, 2019 19:25
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/0e8d1d63dfd13a3b7a9ffd504108af22 to your computer and use it in GitHub Desktop.
Save milseman/0e8d1d63dfd13a3b7a9ffd504108af22 to your computer and use it in GitHub Desktop.
Offset Indexing, no range, explicit end
// TODO: doc
public struct OffsetBound {
internal enum Kind {
case fromStart(Int)
case fromEnd(Int)
}
internal var kind: Kind
internal init(fromStart: Int) {
self.kind = .fromStart(fromStart)
}
internal init(fromEnd: Int) {
self.kind = .fromEnd(fromEnd)
}
}
extension OffsetBound {
// TODO: doc
public static var end: OffsetBound { return OffsetBound(fromEnd: 0) }
// TODO: doc
internal 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)
}
}
public static func +(_ lhs: OffsetBound, _ rhs: Int) -> OffsetBound {
return lhs.advanced(by: rhs)
}
public static func -(_ lhs: OffsetBound, _ rhs: Int) -> OffsetBound {
return lhs.advanced(by: -rhs)
}
// TODO: doc
public func relative<C: Collection>(to c: C) -> C.Index {
switch self.kind {
case .fromStart(let int):
if int < 0 {
fatalError("Let's go with trapping variant here")
}
return c.index(
c.startIndex, offsetBy: int, limitedBy: c.endIndex
) ?? c.endIndex
case .fromEnd(let int):
if int > 0 {
fatalError("Let's go with trapping variant here")
}
return c.index(
c.endIndex, offsetBy: int, limitedBy: c.startIndex
) ?? c.startIndex
}
}
}
extension OffsetBound: ExpressibleByIntegerLiteral {
// TODO: doc
public init(integerLiteral int: Int) {
self.init(fromStart: int)
}
}
extension OffsetBound: Comparable {
public static func < (_ lhs: OffsetBound, _ rhs: OffsetBound) -> Bool {
switch (lhs.kind, rhs.kind) {
case (.fromStart(_), .fromEnd(_)): return true
case (.fromEnd(_), .fromStart(_)): return false
case (.fromStart(let lhs), .fromStart(let rhs)): return lhs < rhs
case (.fromEnd(let lhs), .fromEnd(let rhs)): return lhs < rhs
}
}
public static func == (_ lhs: OffsetBound, _ rhs: OffsetBound) -> Bool {
switch (lhs.kind, rhs.kind) {
case (.fromStart(_), .fromEnd(_)): return false
case (.fromEnd(_), .fromStart(_)): return false
case (.fromStart(let lhs), .fromStart(let rhs)): return lhs == rhs
case (.fromEnd(let lhs), .fromEnd(let rhs)): return lhs == rhs
}
}
}
// To get a Range<OffsetBound> from a RangeExpression<OffsetBound>
//
// TODO: Alternative, if OffsetBound was Strideable, then 0..<.end would work...
internal struct OffsetBoundConverter: Collection {
internal var startIndex: OffsetBound { return OffsetBound(fromStart: 0) }
internal var endIndex: OffsetBound { return OffsetBound(fromEnd: 0) }
internal func index(after bound: OffsetBound) -> OffsetBound {
return bound.advanced(by: 1)
}
internal subscript(bound: OffsetBound) -> OffsetBound { return bound }
internal subscript(bounds: Range<OffsetBound>) -> Range<OffsetBound> {
return bounds
}
init() { }
}
extension RangeExpression where Bound == OffsetBound {
// TODO: doc
internal func _relative<C: Collection>(to c: C) -> Range<C.Index> {
let range = self.relative(to: OffsetBoundConverter())
let lower = range.lowerBound.relative(to: c)
let upper = range.upperBound.relative(to: c)
return lower ..< max(lower, upper)
}
}
extension Collection {
// TODO: doc
public subscript(offset offset: OffsetBound) -> Element {
return self[offset.relative(to: self)]
}
public subscript<ORE: RangeExpression>(
offset range: ORE
) -> SubSequence where ORE.Bound == OffsetBound {
return self[range._relative(to: self)]
}
}
extension MutableCollection {
// TODO: doc
public subscript(offset: OffsetBound) -> Element {
get {
return self[offset.relative(to: self)]
}
set {
self[offset.relative(to: self)] = newValue
}
}
public subscript<ORE: RangeExpression>(
offset range: ORE
) -> SubSequence where ORE.Bound == OffsetBound {
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: .end-4]) // w
print(str[..<idx][offset: .end-100]) // a
print(str[idx...][offset: 1]) // o
// No equivalent: print(str[(idx++1)--10]) // e
print("-- relative range --")
print(str[offset: 1 ..< .end-2]) // bcdefghijklmnopqrstuvwx
// No equivalent: print(str[..<idx--2 ..< --2]) // lmnopqrstuvwx
print(str[idx...][offset: ..<(.end-2)]) // nopqrstuvwx
print(str[..<idx][offset: (.end-2)...]) // lm
// No equivalent: print(str[idx--2..<idx++3]) // lmnop
print(str[offset: .end-4 ..< .end-2]) // wx
print("-- relative range through --")
// No equivalent: print(str[idx--2 ... --2]) // lmnopqrstuvwxy
print(str[idx...][offset: ...(.end-2)]) // nopqrstuvwxy
print(str[...idx][offset: (.end-3)...]) // lmn
// No equivalent: print(str[idx--2...idx++3]) // lmnopq
print(str[offset: .end-4 ... .end-2]) // wxy
print("-- partial relative range up to --")
// No equivalent: print(str[..<idx++2]) // abcdefghijklmno
print(str[..<idx][offset: ..<(.end-2)]) // abcdefghijk
print(str[offset: ..<20]) // abcdefghijklmnopqrst
print(str[offset: ..<(.end-20)]) // abcdef
print("-- partial relative range through --")
// No equivalent: print(str[...idx++2]) // abcdefghijklmnop
print(str[...idx][offset: ...(.end-3)]) // abcdefghijkl
print(str[offset: ...20]) // abcdefghijklmnopqrstu
print(str[offset: ...(.end-20)]) // abcdefg
print(str[offset: ...(.end-20)][offset: ...(.end-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: (.end-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: ClosedRange<OffsetBound> = 1...(.end-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..<(.end-1)]) // b
print("abc"[offset: 1..<(.end-2)]) // ""
print("abc"[offset: 1..<(.end-3)]) // ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment