Created
May 3, 2019 21:28
-
-
Save milseman/7f7cf3b764618ead6011700fdce2ad83 to your computer and use it in GitHub Desktop.
Offset Indexing, only OffsetBound
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
// TODO: doc | |
public struct OffsetBound { | |
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) } | |
} | |
} | |
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> | |
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() { } | |
} | |
internal var offsetBoundConverter: OffsetBoundConverter { | |
return OffsetBoundConverter() | |
} | |
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) | |
} | |
} | |
// TODO: Throw in the intersection of existing conformances on range types | |
// extension OffsetBound { | |
// // TODO: doc | |
// public static func ..< ( | |
// lhs: OffsetBound, rhs: OffsetBound | |
// ) -> OffsetRange { | |
// return OffsetRange(lower: lhs, upper: rhs) | |
// } | |
// // TODO: doc | |
// public static func ... ( | |
// lhs: OffsetBound, rhs: OffsetBound | |
// ) -> OffsetRange { | |
// return OffsetRange(lower: lhs, upper: rhs.advanced(by: 1)) | |
// } | |
// // TODO: doc | |
// public static prefix func ..< ( | |
// maximum: OffsetBound | |
// ) -> OffsetRange { | |
// return OffsetRange(partialTo: maximum) | |
// } | |
// // TODO: doc | |
// public static prefix func ... ( | |
// maximum: OffsetBound | |
// ) -> OffsetRange { | |
// return OffsetRange(partialTo: maximum.advanced(by: 1)) | |
// } | |
// // TODO: doc | |
// public static postfix func ... ( | |
// minimum: OffsetBound | |
// ) -> OffsetRange { | |
// 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) -> 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: -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: ClosedRange<OffsetBound> = 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) -> 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