Skip to content

Instantly share code, notes, and snippets.

@beccadax
Created July 9, 2016 01:31
Show Gist options
  • Save beccadax/b36ef130873b752d4c6f7ee3c157d07d to your computer and use it in GitHub Desktop.
Save beccadax/b36ef130873b752d4c6f7ee3c157d07d to your computer and use it in GitHub Desktop.
Non-stdlib initial prototype of IncompleteRange design
//: Playground - noun: a place where people can play
func withSampleData(f: (inout [Int]) -> Void) {
var array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
f(&array)
}
// IncompleteRange, nil lowerBound
withSampleData { array in
array[..<6]
array.prefix(upTo: 6)
// On other (unproposed) APIs:
array.replaceSubrange(..<1, with: [0])
array.removeSubrange(..<1)
}
// IncompleteClosedRange, nil lowerBound
withSampleData { array in
array[...6]
array.prefix(through: 6)
// On other (unproposed) APIs:
array.replaceSubrange(...1, with: [1])
array.removeSubrange(...1)
}
// IncompleteRange, nil upperBound
withSampleData { array in
array[6..<]
array.suffix(from: 6)
// On other (unproposed) APIs:
array.replaceSubrange(6..<, with: [10])
array.removeSubrange(6..<)
}
// IncompleteClosedRange, nil upperBound
// These are all commented out because they crash, and they crash because they cover `endIndex`. Should postfix `...` be left out as seemingly useless, or kept for symmetry?
//withSampleData { array in
// array[6...]
// // No equivalent
// // On other (unproposed) APIs:
// array.replaceSubrange(6..., with: [11])
// array.removeSubrange(6...)
//}
// Test that the possible infix form doesn't introduce new conflicts
withSampleData { array in
array[1 ..< 6]
array[nil ..< 6]
array[1 ..< nil]
array[nil ..< nil]
}
extension Collection {
internal func _fullRange() -> Range<Index> {
return startIndex ..< endIndex
}
internal func _fullRange() -> ClosedRange<Index> {
return startIndex ... endIndex
}
/// Accesses a contiguous subrange of the collection's elements, with the
/// missing bounds filled in with `startIndex` and `endIndex`.
///
/// The accessed slice uses the same indices for the same elements as the
/// original collection. Always use the slice's `startIndex` property
/// instead of assuming that its indices start at a particular value.
///
/// This example demonstrates getting a slice of an array of strings, finding
/// the index of one of the strings in the slice, and then using that index
/// in the original array.
///
/// let streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"]
/// let streetsSlice = streets[2..<]
/// print(streetsSlice)
/// // Prints "["Channing", "Douglas", "Evarts"]"
///
/// let index = streetsSlice.index(of: "Evarts") // 4
/// print(streets[index!])
/// // Prints "Evarts"
///
/// - Parameter bounds: A range of the collection's indices. The bounds of
/// the range must be valid indices of the collection. A `nil` `lowerBound`
/// will be interpreted as `startIndex`; a `nil` `upperBound` will be
/// interpreted as `endIndex`.
public subscript(range: IncompleteClosedRange<Index>) -> SubSequence {
return self[range.completed(with: _fullRange())]
}
/// Accesses a contiguous subrange of the collection's elements, with the
/// missing bounds filled in with `startIndex` and `endIndex`.
///
/// The accessed slice uses the same indices for the same elements as the
/// original collection uses. Always use the slice's `startIndex` property
/// instead of assuming that its indices start at a particular value.
///
/// This example demonstrates getting a slice of an array of strings, finding
/// the index of one of the strings in the slice, and then using that index
/// in the original array.
///
/// let streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"]
/// let streetsSlice = streets[2..<]
/// print(streetsSlice)
/// // Prints "["Channing", "Douglas", "Evarts"]"
///
/// let index = streetsSlice.index(of: "Evarts") // 4
/// print(streets[index!])
/// // Prints "Evarts"
///
/// - Parameter bounds: A range of the collection's indices. The bounds of
/// the range must be valid indices of the collection. A `nil` `lowerBound`
/// will be interpreted as `startIndex`; a `nil` `upperBound` will be
/// interpreted as `endIndex`.
public subscript(range: IncompleteRange<Index>) -> SubSequence {
return self[range.completed(with: _fullRange())]
}
}
// These are future expansions: other APIs which could benefit from incomplete range support.
extension RangeReplaceableCollection {
public mutating func removeSubrange(_ bounds: IncompleteRange<Self.Index>) {
removeSubrange(bounds.completed(with: _fullRange()))
}
public mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == _Element>(_ subrange: IncompleteRange<Self.Index>, with newElements: C) {
replaceSubrange(subrange.completed(with: _fullRange()), with: newElements)
}
public mutating func removeSubrange(_ bounds: IncompleteClosedRange<Self.Index>) {
removeSubrange(bounds.completed(with: _fullRange()))
}
public mutating func replaceSubrange<C where C : Collection, C.Iterator.Element == Iterator.Element>(_ subrange: IncompleteClosedRange<Self.Index>, with newElements: C) {
replaceSubrange(subrange.completed(with: _fullRange()), with: newElements)
}
}
prefix operator ... {}
postfix operator ... {}
/// A closed interval which may have one or both of its bounds unknown. A
/// completed `ClosedRange` or `CountableClosedRange` can be created from an
/// `IncompleteClosedRange` by calling the `completed(with:)` method.
public struct IncompleteClosedRange<Bound: Comparable> {
public let lowerBound: Bound?
public let upperBound: Bound?
/// Returns a `ClosedRange` with the bounds of `self`, with any missing bounds
/// filled in from `defaultBounds`.
public func completed(with defaultBounds: ClosedRange<Bound>) -> ClosedRange<Bound> {
return (lowerBound ?? defaultBounds.lowerBound) ... (upperBound ?? defaultBounds.upperBound)
}
}
extension IncompleteClosedRange where Bound: Strideable, Bound.Stride: SignedInteger {
/// Returns a `CountableClosedRange` with the bounds of `self`, with any
/// missing bounds filled in from `defaultBounds`.
public func completed(with defaultBounds: CountableClosedRange<Bound>) -> CountableClosedRange<Bound> {
return (lowerBound ?? defaultBounds.lowerBound) ... (upperBound ?? defaultBounds.upperBound)
}
}
/// Constructs an `IncompleteClosedRange` with the indicated `lowerBound`. The
/// `upperBound` is left empty and will be filled in when the range is completed.
public postfix func ... <Bound: Comparable>(lowerBound: Bound) -> IncompleteClosedRange<Bound> {
return IncompleteClosedRange(lowerBound: lowerBound, upperBound: nil)
}
/// Constructs an `IncompleteClosedRange` with the indicated `upperBound`. The
/// `lowerBound` is left empty and will be filled in when the range is completed.
public prefix func ... <Bound: Comparable>(upperBound: Bound) -> IncompleteClosedRange<Bound> {
return IncompleteClosedRange(lowerBound: nil, upperBound: upperBound)
}
prefix operator ..< {}
postfix operator ..< {}
/// A half-open interval which may have one or both of its bounds unknown. A
/// completed `Range` or `CountableRange` can be created from an
/// `IncompleteRange` by calling the `completed(with:)` method.
public struct IncompleteRange<Bound: Comparable> {
public let lowerBound: Bound?
public let upperBound: Bound?
/// Returns a `Range` with the bounds of `self`, with any missing bounds filled
/// in from `defaultBounds`.
public func completed(with defaultBounds: Range<Bound>) -> Range<Bound> {
return (lowerBound ?? defaultBounds.lowerBound) ..< (upperBound ?? defaultBounds.upperBound)
}
}
extension IncompleteRange where Bound: Strideable, Bound.Stride: SignedInteger {
/// Returns a `CountableRange` with the bounds of `self`, with any missing
/// bounds filled in from `defaultBounds`.
public func completed(with defaultBounds: CountableRange<Bound>) -> CountableRange<Bound> {
return (lowerBound ?? defaultBounds.lowerBound) ..< (upperBound ?? defaultBounds.upperBound)
}
}
/// Constructs an `IncompleteRange` with the indicated `lowerBound`. The
/// `upperBound` is left empty and will be filled in when the range is completed.
public postfix func ..< <Bound: Comparable>(lowerBound: Bound) -> IncompleteRange<Bound> {
return IncompleteRange(lowerBound: lowerBound, upperBound: nil)
}
/// Constructs an `IncompleteRange` with the indicated `upperBound`. The
/// `lowerBound` is left empty and will be filled in when the range is completed.
public prefix func ..< <Bound: Comparable>(upperBound: Bound) -> IncompleteRange<Bound> {
return IncompleteRange(lowerBound: nil, upperBound: upperBound)
}
// XXX I notice that, although IncompleteRange permits the construction of ranges
// with both or neither bound specified, this design doesn't include operators that
// would allow that. If there were infix ..< and ... operators, we would be able
// to.
public func ..< <Bound: Comparable>(lowerBound: Bound?, upperBound: Bound?) -> IncompleteRange<Bound> {
return IncompleteRange(lowerBound: lowerBound, upperBound: upperBound)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment