Skip to content

Instantly share code, notes, and snippets.

@beccadax
Created July 26, 2016 12:28
Show Gist options
  • Save beccadax/0946a99528f6e6500d93bbf67684c0b3 to your computer and use it in GitHub Desktop.
Save beccadax/0946a99528f6e6500d93bbf67684c0b3 to your computer and use it in GitHub Desktop.
Alternate design for RelativeRange
let array = Array(1...10)
array[.startIndex ..< 3]
array[.startIndex + 2 ..< .endIndex - 1]
public protocol RangeExpression {
/// The type of the bounds of the `Range` produced by this
/// type when used as a `RangeExpression`.
associatedtype Bound : Comparable
/// Returns `self` expressed as a `Range<Bound>` suitable for
/// slicing a collection with the indicated properties.
///
/// -Parameter bounds: The range of indices in the collection.
/// Equivalent to `startIndex ..< endIndex`
/// in `Collection`.
///
/// -Parameter offset: A function which can be used to add to or
/// subtract from a bound. Equivalent to
/// `index(_:offsetBy:)` in `Collection`.
///
/// -Returns: A `Range<Bound>` suitable for slicing a collection.
/// The return value is *not* guaranteed to be inside
/// `bounds`. Callers should apply the same preconditions
/// to the return value as they would to a range provided
/// directly by the user.
///
/// -Warning: This method is likely to be replaced in a future version of Swift.
/// If you are calling this method, we recommend using the
/// `relative(to:)` extension method instead. If you are implementing
/// it, be prepared to migrate your code.
///
/// -Recommended: `relative(to:)`
//
// WORKAROUND unfiled - We want to have this requirement, but it triggers a generics bug
// func relative<C: Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound
func relative<BoundDistance: SignedInteger>(to bounds: Range<Bound>, offsettingBy offset: (Bound, BoundDistance) -> Bound) -> Range<Bound>
}
extension RangeExpression {
/// Returns `self` expressed as a range of indices within `collection`.
///
/// -Parameter collection: The collection `self` should be
/// relative to.
///
/// -Returns: A `Range<Bound>` suitable for slicing `collection`.
/// The return value is *not* guaranteed to be inside
/// its bounds. Callers should apply the same preconditions
/// to the return value as they would to a range provided
/// directly by the user.
///
/// -RecommendedOver: `relative(to:offsettingBy:)`
public func relative<C: Indexable>(to collection: C) -> Range<Bound> where C.Index == Bound {
let bounds = Range(uncheckedBounds: (lower: collection.startIndex, upper: collection.endIndex))
return relative(to: bounds, offsettingBy: collection.index(_:offsetBy:))
}
}
enum RelativeBound<Bound: Comparable> {
case from(Bound, offset: Int)
case fromStart(offset: Int)
case fromEnd(offset: Int)
static var startIndex: RelativeBound<Bound> { return fromStart(offset: 0) }
static var endIndex: RelativeBound<Bound> { return fromEnd(offset: 0) }
func destructured(with bounds: Range<Bound>) -> (Bound, Int) {
switch self {
case let .from(bound, offset):
return (bound, offset)
case let .fromStart(offset):
return (bounds.lowerBound, offset)
case let .fromEnd(offset):
return (bounds.upperBound, offset)
}
}
func relative<BoundDistance : SignedInteger>(to bounds: Range<Bound>, offsettingBy offset: (Bound, BoundDistance) -> Bound) -> Bound {
let (bound, offsetDistance) = destructured(with: bounds)
return offset(bound, BoundDistance(offsetDistance.toIntMax()))
}
}
func + <Bound: Comparable>(lhs: RelativeBound<Bound>, rhs: Int) -> RelativeBound<Bound> {
switch lhs {
case .fromEnd(let lhsInt):
return .fromEnd(offset: lhsInt + rhs)
case .fromStart(let lhsInt):
return .fromStart(offset: lhsInt + rhs)
case let .from(bound, offset):
return .from(bound, offset: offset + rhs)
}
}
func - <Bound: Comparable>(lhs: RelativeBound<Bound>, rhs: Int) -> RelativeBound<Bound> {
return lhs + -rhs
}
struct RelativeRange<Bound: Comparable> {
let lowerBound: RelativeBound<Bound>
let upperBound: RelativeBound<Bound>
}
extension RelativeRange: RangeExpression {
func relative<BoundDistance : SignedInteger>(to bounds: Range<Bound>, offsettingBy offset: (Bound, BoundDistance) -> Bound) -> Range<Bound> {
return lowerBound.relative(to: bounds, offsettingBy: offset) ..< upperBound.relative(to: bounds, offsettingBy: offset)
}
}
func ..< <Bound: Comparable>(lhs: RelativeBound<Bound>, rhs: RelativeBound<Bound>) -> RelativeRange<Bound> {
return RelativeRange(lowerBound: lhs, upperBound: rhs)
}
func ..< <Bound: Comparable>(lhs: RelativeBound<Bound>, rhs: Bound) -> RelativeRange<Bound> {
return lhs ..< .from(rhs, offset: 0)
}
func ..< <Bound: Comparable>(lhs: Bound, rhs: RelativeBound<Bound>) -> RelativeRange<Bound> {
return .from(lhs, offset: 0) ..< rhs
}
extension Indexable {
subscript(bounds: RelativeRange<Index>) -> SubSequence {
return self[bounds.relative(to: self)]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment