Created
May 6, 2019 19:25
-
-
Save milseman/0e8d1d63dfd13a3b7a9ffd504108af22 to your computer and use it in GitHub Desktop.
Offset Indexing, no range, explicit end
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 | |
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