I ran across some funky Range behavior and now I'm questioning everything I thought I knew about Range in Swift.
let range = Range<Int>(start: 0, end: 2)
print(range.count) // Prints 2
Since Range uses a start & end instead of a location & length that NSRange uses I would expect the range above to have a count of 3. It almost seems like it is being treated like an NSRange since a count of 2 makes sense if your location = 0 and length = 2.
let array = ["A", "B", "C", "D"]
let slice = array[range]
I would expect slice to contain ABC since range's end index is 2, but slice actually contains AB, which does correspond to the range.count == 2, but doesn't add up since the range's endIndex == 2 which should include C.
What am I missing here?
I'm using Xcode 7.2's version of Swift, not any of the open source versions.
Range objects Range<T> in Swift are, by default, presented as a half-open interval [start,end), i.e. start..<end (see HalfOpenInterval IntervalType).
You can see this if your print your range variable
let range = Range<Int>(start: 0, end: 2)
print(range) // 0..<2
Also, as Leo Dabus pointed out below (thanks!), you can simplify the declaration of Range<Int> by using the half-open range operator ..< directly:
let range = 0..<2
print(range) // 0..<2 (naturally)
Likewise, you can declare Range<Int> ranges using the closed range operator ...
let range = 0...1
print(range) // 0..<2
And we see, interestingly (in context of the question), that this again verifies that the default representation of Ranges are by means of the half-open operator.
That the half-open interval is default for Range is written somewhat implicitly, in text, in the language reference for range:
Like other collections, a range containing one element has an endIndex
that is the successor of its startIndex; and an empty range has
startIndex == endIndex.
Range conforms, however, to CollectionType protocol. In the language reference to the latter, it's stated clearly that the startIndex and endIndex defines a half-open interval:
The sequence view of the elements is identical to the collection view.
In other words, the following code binds the same series of values to
x as does for x in self {}:
for i in startIndex..<endIndex {
let x = self[i]
}
To wrap it up; Range is defined as half-open interval (startIndex ..< endIndex), even if it's somewhat obscure to find in the docs.
See also
Swift Language Guide - Basic Operators - Range Operators
Swift includes two range operators, which are shortcuts for expressing
a range of values.
...
The closed range operator (a...b) defines a range that runs from a to
b, and includes the values a and b. The value of a must not be greater
than b.
...
The half-open range operator (a..< b) defines a range that runs from a
to b, but does not include b. It is said to be half-open because it
contains its first value, but not its final value.
Related
In the following Swift code:
self.numbers[2] = Int.random(in: 0...self.symbols.count-1)
Why do we have to write -1?
I don't understand the code.
When you are using A...B, that range includes B in it. See ...(_:_:) docs. So if you say -
Int.random(in: 0...self.symbols.count-1)
It means range starts at 0 and ends at symbols.count-1 including both.
Say an array has 2 elements, it's count is 2, but valid indices are 0, 1 (2 is not a valid index), so you are just making sure that it is restricted to valid index values.
Other way to write the same would be - A..<B, in this range, B is not included. See ..<(_:_:) docs. Following is same as above.
Int.random(in: 0..<self.symbols.count)
You are using ClosedRange here. It is a range which includes both start and end value of the range. You can also used simple Range, which is open so to say. It only includes start value and not the final value.
Lets take an example.
let a = 1 ... 10 // includes all values from 1 to 10
let b = 1 ..< 10 // includes values from 1 to 9
So, basically what you are writing here,
self.numbers[2] = Int.random(in: 0 ... self.symbols.count - 1)
is equivalent to open range using,
self.numbers[2] = Int.random(in: 0 ..< self.symbols.count)
such that you dont need to add -1 to it.
Int.random can take both ClosedRange and Range, here and here.
Here are your code:
self.numbers[2] = Int.random(in: 0...self.symbols.count-1)
It says: your third item in numbers array will be assigned by the random item of symbols array
But how to get the random item of symbols array? You'll get it by its index.
The indexes of an array starting from 0, so, for the last element of array, its index will be the array length - 1, for example, with [1,2,3], the last element's index will be 2.
Here, you used Closed Range, it will include the last element, so if you don't minus count by one, the last index will be 3 (because array.count will return the length of the array), which is produce out of index error
In Swift 3, I wonder why I'm able to use the half-open range operator ..< in Data.subdata(in:) but not the closed range operator ....
I've searched everywhere but can't understand why it gives me this error :
no '...' candidates produce the expected contextual result type
'Range' (aka 'Range')
Here's an example of both the one that works and the one doesn't :
import Foundation
let x = Data(bytes: [0x0, 0x1])
let y : UInt8 = x.subdata(in: 0..<2).withUnsafeBytes{$0.pointee}
let z : UInt8 = x.subdata(in: 0...1).withUnsafeBytes{$0.pointee} // This fails
Thanks!
..< is the half-open range operator, which can either create a Range or CountableRange (depending on whether the Bound is Strideable with an Integer Stride or not). The range that is created is inclusive of the lower bound, but exclusive of the upper bound.
... is the closed range operator, which can either create a ClosedRange or CountableClosedRange (same requirements as above). The range that is created is inclusive of both the upper and lower bounds.
Therefore as subdata(in:) expects a Range<Int>, you cannot use the closed range operator ... in order to construct the argument – you must use the half-open range operator instead.
However, it would be trivial to extend Data and add an overload that does accept a ClosedRange<Int>, which would allow you to use the closed range operator.
extension Data {
func subdata(in range: ClosedRange<Index>) -> Data {
return subdata(in: range.lowerBound ..< range.upperBound + 1)
}
}
let x = Data(bytes: [0x0, 0x1])
let z : UInt8 = x.subdata(in: 0...1).withUnsafeBytes {$0.pointee}
At-risk of my comment being closed I'll speak out anyway - I followed the advice of a closed/deleted comment as the selected answer did not work with Swift 5. The working solution was:
x.subdata(in: Range(0...1))
I assume this has to do with interpretations of "Range" vs "NSRange" - but honestly I don't know.
This comparison worked in Swift 2 but doesn't anymore in Swift 3:
let myStringContainsOnlyOneCharacter = mySting.rangeOfComposedCharacterSequence(at: myString.startIndex) == mySting.characters.indices
How do I compare Range and DefaultBidirectionalIndices?
From SE-0065 – A New Model for Collections and Indices
In Swift 2, collection.indices returned a Range<Index>, but because a range is a simple pair of indices and indices can no longer be advanced on their own, Range<Index> is no longer iterable.
In order to keep code like the above working, Collection has acquired an associated Indices type that is always iterable, ...
Since rangeOfComposedCharacterSequence returns a range of
character indices, the solution is not to use indices, but
startIndex..<endIndex:
myString.rangeOfComposedCharacterSequence(at: myString.startIndex)
== myString.startIndex..<myString.endIndex
As far as I know, String nor String.CharacterView does not have a concise method returning Range<String.Index> or something comparable to it.
You may need to create a Range explicitly with range operator:
let myStringContainsOnlyOneCharacter = myString.rangeOfComposedCharacterSequence(at: myString.startIndex)
== myString.startIndex..<myString.endIndex
Or compare only upper bound, in your case:
let onlyOne = myString.rangeOfComposedCharacterSequence(at: myString.startIndex).upperBound
== myString.endIndex
I'm having an issue trying to access the nth element of a Range using subscripts. The code is super simple:
var range = 0..<9
var itemInRange = range[n] // n is some Int where 0 <= n < 9
The second line complains with the error Ambiguous use of "subscript", which I took to mean that Xcode wasn't clear what the type of the variable range is, and so it's unable to know which implementation of subscript to use. I tried to fix this by explicitly defining the type of range with
var range: Range<Int> = 0..<9
and
var firstInRange = (range as Range<Int>)[0]
but neither of these solved the problem. Is there a way to get Xcode to disambiguate the call to subscript?
You can create an array with the range and then pick an element from the array.
var range = [Int](0..<9)
var itemInRange = range[1]
From apple docs
A collection of consecutive discrete index values.
Like other collections, a range containing one element has an endIndex
that is the successor of its startIndex; and an empty range has
startIndex == endIndex.
Axiom: for any Range r, r[i] == i.
Therefore, if Element has a maximal value, it can serve as an
endIndex, but can never be contained in a Range.
It also follows from the axiom above that (-99..<100)[0] == 0. To
prevent confusion (because some expect the result to be -99), in a
context where Element is known to be an integer type, subscripting
with Element is a compile-time error:
// error: could not find an overload for 'subscript'...
print(Range(start: -99, end: 100)[0])
https://developer.apple.com/library/prerelease/mac/documentation/Swift/Reference/Swift_Range_Structure/index.html
So I have an instance of Range<String.Index> obtained from a search method. And also a standalone String.Index by other means how can I tell wether this index is within the aforementioned range or not?
Example code:
let str = "Hello!"
let range = Range(start: str.startIndex, end: str.endIndex)
let anIndex = advance(str.startIndex, 3)
// How to tell if `anIndex` is within `range` ?
Since comparison operators do not work on String.Index instances, the only way seems to be to perform a loop through the string using advance but this seems overkill for such a simple operation.
The beta 5 release notes mention:
The idea of a Range has been split into three separate concepts:
Ranges, which are Collections of consecutive discrete ForwardIndexType values. Ranges are used for slicing and iteration.
Intervals over Comparable values, which can efficiently check for containment. Intervals are used for pattern matching in switch
statements and by the ~= operator.
Striding over Strideable values, which are Comparable and can be advanced an arbitrary distance in O(1).
Efficient containment checking is what you want, and this is possible since String.Index is Comparable:
let range = str.startIndex..<str.endIndex as HalfOpenInterval
// or this:
let range = HalfOpenInterval(str.startIndex, str.endIndex)
let anIndex = advance(str.startIndex, 3)
range.contains(anIndex) // true
// or this:
range ~= anIndex // true
(For now, it seems that explicitly naming HalfOpenInterval is necessary, otherwise the ..< operator creates a Range by default, and Range doesn't support contains and ~= because it uses only ForwardIndexType.)