Created
May 2, 2019 22:45
-
-
Save milseman/c38002fe6c2a7251b5a970f6d1141cf2 to your computer and use it in GitHub Desktop.
Offset Indexing, using 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 { | |
internal enum Kind { | |
case fromStart(Int) | |
case fromEnd(Int) | |
} | |
internal var kind: Kind | |
init(fromStart: Int) { | |
self.kind = .fromStart(fromStart) | |
} | |
init(fromEnd: Int) { | |
self.kind = .fromEnd(fromEnd) | |
} | |
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) | |
} | |
} | |
} | |
extension OffsetBound { | |
// TODO: doc, extra docs mentioning that 0 and more is from start, negative | |
// from end. | |
public init(_ int: Int) { | |
if int < 0 { self.init(fromEnd: int) } else { self.init(fromStart: int) } | |
} | |
// TODO: doc | |
public func index<C: Collection>(for c: C) -> C.Index { | |
switch self.kind { | |
case .fromStart(let int): | |
return c.index( | |
c.startIndex, offsetBy: int, limitedBy: c.endIndex | |
) ?? c.endIndex | |
case .fromEnd(let int): | |
return c.index( | |
c.endIndex, offsetBy: int, limitedBy: c.startIndex | |
) ?? c.startIndex | |
} | |
} | |
} | |
extension OffsetBound: ExpressibleByIntegerLiteral { | |
// TODO: doc | |
public init(integerLiteral: Int) { self.init(integerLiteral) } | |
} | |
// TODO: doc | |
public struct OffsetRange { | |
internal enum Kind { | |
case full(lower: OffsetBound, upper: OffsetBound) | |
case partialFrom(lower: OffsetBound) | |
case partialTo(upper: OffsetBound) | |
} | |
internal var kind: Kind | |
// lower ..< upper | |
init(lower: OffsetBound, upper: OffsetBound) { | |
self.kind = .full(lower: lower, upper: upper) | |
} | |
// lower ... upper | |
init(lower: OffsetBound, through: OffsetBound) { | |
self.init(lower: lower, upper: through.advanced(by: 1)) | |
} | |
// from ... | |
init(partialFrom from: OffsetBound) { | |
self.kind = .partialFrom(lower: from) | |
} | |
// ..< to | |
init(partialTo to: OffsetBound) { | |
self.kind = .partialTo(upper: to) | |
} | |
} | |
extension OffsetRange { | |
// TODO: doc | |
public func index<C: Collection>(for c: C) -> Range<C.Index> { | |
switch self.kind { | |
case .full(let lower, let upper): | |
let lb = lower.index(for: c) | |
return lb ..< max(lb, upper.index(for: c)) | |
case .partialFrom(let lower): | |
return lower.index(for: c) ..< c.endIndex | |
case .partialTo(let upper): | |
return c.startIndex ..< upper.index(for: c) | |
} | |
} | |
} | |
// 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) | |
} | |
} | |
extension Collection { | |
// TODO: doc | |
public subscript(offset offset: OffsetBound) -> Element { | |
return self[offset.index(for: self)] | |
} | |
public subscript(offset range: OffsetRange) -> SubSequence { | |
return self[range.index(for: self)] | |
} | |
} | |
extension MutableCollection { | |
// TODO: doc | |
public subscript(offset: OffsetBound) -> Element { | |
get { | |
return self[offset.index(for: self)] | |
} | |
set { | |
self[offset.index(for: self)] = newValue | |
} | |
} | |
public subscript(offset: OffsetRange) -> SubSequence { | |
get { | |
return self[offset.index(for: self)] | |
} | |
set { | |
self[offset.index(for: 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+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 range: OffsetRange = 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)]) // "" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment