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
Related
I was trying to implement a small iteration which returns the square of some ranges.
Which should be the equivalence of this Python script
for i in range(n):
print(i*i)
In Swift I tried
first attempt
let numbers = [1..<10]
for i in numbers{
print(i*i)
}
and
second attmpt
let numbers = [1..<10]
for i in numbers{
var j: Int = i
print(j*j)
}
but then the compiler says
Cannot convert value of type 'Range<Int>' to specified type 'Int'
I understand from my python experience this is due to different types in Swift. Thus my questions are
How can I fix this? (i.e. implement the same thing i did in python)
What are the problems with my first and second attempts?
Why are there so many types of <Int> in Swift?
Thanks in advance!
Your code doesn't compile because you have used [] around the range, which creates an array. [1..<10] is an array of ranges. The for loop is then iterating over that array, which has only one element - the range 1..<10.
This is why i is of type Range<Int>. It is the range, not the numbers in the range.
Just remove the [] and both of your code would work. You can iterate over ranges directly (in fact, anything that conforms to the Sequence protocol), not just arrays. You can even write the range inline with the loop:
for i in 0..<10 {
print(i * i)
}
Why are there so many types of <Int> in Swift?
You are looking at this the wrong way, the word Range and ClosedRange in the types Range<Int> and ClosedRange<Int> are not words that modify Int, as if they are different "flavours" of Int. It's the opposite - Range<Bound> and ClosedRange<Bound> are generic types, and Range<Int> can be a considered the specific "type" of Range that has Int as its bounds. You can also have Range<Float> or Range<UInt8> for example.
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 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.
x is an object that holds an array called point.
x implements the subscript operator so you can do things, like x[i] to get the array's ith element (of type T, which is usually an Int or Double).
This is what I want to do:
x[0...2] = [0...2]
But I get an error that says ClosedInterval<T> is not convertible to Int/Double.
Edit1:
Here is my object x:
let x = Point<Double>(dimensions:3)
For kicks and giggles: define x as [1.0,2.0,0.0]
I can get the first n elements via x[0...2].
What I want to know is how to update x[0...2] to hold [0.0, 0.0.0.0] in one fell swoop. Intuitively, I would want to do x[0...2] = [0...2]. This does not work as can be seen in the answers. I want to update x without iteration (on my end) and by hiding the fact that x is not an array (even though it is not).
[0...2] is an array with one element which, at best, will be a Range<Int> from 0 through 2. You can't assign that to a slice containing, say, Ints.
x[0...2] on the other hand is (probably) a slice, and Sliceable only defines a get subscript, not a setter. So even if the types were more compatible - that is, if you tried x[0...2] = 0...2, which at least is attempting to replace a range within x with the values of a similarly-sized collection - it still wouldn't work.
edit: as #rintaro points out, Array does support a setter subscript for ranges – so if x were a range you could do x[0...2] = Slice(0...2) – but it has to be a slice you assign, so I'd still go with replaceRange.
If what you mean is you want to replace entries 0 through 2 with some values, what you want is replaceRange, as long as your collection conforms to RangeReplaceableCollection (which, for example, Array does):
var x = [0,1,2,3,4,5]
var y = [200,300,400]
x.replaceRange(2..<5, with: y)
// x is now [0,1,200,300,400,5]
Note, the replaced range and y don't have to be the same size, the collection will expand/contract as necessary.
Also, y doesn't have to an array, it can be any kind of collection (has to be a collection though, not a sequence). So the above code could have been written as:
var x = [0,1,2,3,4,5]
var y = lazy(2...4).map { $0 * 100 }
x.replaceRange(2..<5, with: y)
edit: so, per your edit, to in-place zero out an array of any size in one go, you can do:
var x = [1.0,2.0,0.0]
// range to replace is the whole array's range,
// Repeat just generates any given value n times
x.replaceRange(indices(x), with: Repeat(count: x.count, repeatedValue: 0.0))
Adjust the range (and count of replacing entries) accordingly if you want to just zero out a subrange.
Given your example Point class, here is how you could implement this behavior assuming it's backed by an array under the hood:
struct Point<T: FloatLiteralConvertible> {
private var _vals: [T]
init(dimensions: Int) {
_vals = Array(count: dimensions, repeatedValue: 0.0)
}
mutating func replaceRange
<C : CollectionType where C.Generator.Element == T>
(subRange: Range<Array<T>.Index>, with newElements: C) {
// just forwarding on the request - you could perhaps
// do some additional validation first to ensure dimensions
// aren't being altered...
_vals.replaceRange(subRange, with: newElements)
}
}
var x = Point<Double>(dimensions:3)
x.replaceRange(0...2, with: [1.1,2.2,3.3])
You need to implement subscript(InvervalType) to handle the case of multiple assignments like this. That isn't done for you automatically.