Last active
May 3, 2019 00:21
-
-
Save milseman/1f4b998cad821d6e6853a7567c3e099d to your computer and use it in GitHub Desktop.
Offset Indexing, using phantom-typed OffsetRange
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<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