Created
May 3, 2019 22:25
-
-
Save milseman/bd7547d87a708a2d8dcec663596354ba to your computer and use it in GitHub Desktop.
Broken: Offset Indexing, Existing ranges, Phantom typed
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<Bound> { | |
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 where Bound == 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<Bound>: Collection { | |
typealias Element = OffsetBound<Bound> | |
typealias Index = OffsetBound<Bound> | |
internal var startIndex: Index { return OffsetBound(fromStart: 0) } | |
internal var endIndex: Index { return OffsetBound(fromEnd: 0) } | |
internal func index(after bound: Index) -> Index { | |
return bound.advanced(by: 1) | |
} | |
internal subscript(bound: Index) -> Element { return bound } | |
internal subscript(bounds: Range<Index>) -> Range<Element> { | |
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<C.Index>()) | |
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<Index>) -> Element { | |
return self[offset.relative(to: self)] | |
} | |
public subscript<ORE: RangeExpression>( | |
offset range: ORE | |
) -> SubSequence where ORE.Bound == OffsetBound<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<ORE: RangeExpression>( | |
offset range: ORE | |
) -> SubSequence where ORE.Bound == OffsetBound<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: ClosedRange<OffsetBound<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