Ranges and the ternary operator
A museum sells a child ticket to kids aged 3 to 12. A twelve-year-old gets one: the boundary belongs to the stretch. A shop is open from 9:00 to 18:00, but at 18:00 the doors are already shut: the second boundary is the moment the stretch ends.
Swift has an operator for each case. Both create a range: a stretch of numbers from a lower bound to an upper bound.
The closed range ...
a...b includes both bounds, like the child ticket:
let childAges = 3...12
print(childAges.contains(12)) // true
print(childAges.contains(13)) // falseLet's go line by line:
3...12is every whole number from 3 to 12, 12 included.containschecks whether a number falls in the range. The range itself knows how to do this, and you call it with a dot:childAges.contains(12)givestrue.- The number 13 is not in the range, and
contains(13)givesfalse.
The lower bound cannot be larger than the upper one. 12...3 does not give an empty range; it crashes the program:
Fatal error: Range requires lowerBound <= upperBoundThe half-open range ..<
a..<b stops before b, like the shop's opening hours:
let openHours = 9..<18
print(openHours.contains(9)) // true
print(openHours.contains(18)) // falseThe lower bound 9 is in the range, and the upper bound 18 is not. Here the clock picture is inaccurate: 9..<18 holds only the whole numbers 9 to 17, while real time also has minutes.
A half-open range is handy when the upper bound means an end or a count: 0..<5 holds exactly five numbers, from 0 to 4.
Interviewers for junior roles often ask for the difference between ... and ..<. The short answer: the first includes the upper bound, the second stops before it.
Parentheses around a range
To call contains on a range written out in place, put it in parentheses: (1...5).contains(5). Without them Swift applies .contains to the number 5 alone:
error: value of type 'Int' has no member 'contains'The ternary operator
You often need to pick one of two values: a child ticket or an adult one. That is what the ternary operator is for: condition ? a : b. Its name means "made of three parts": a condition and two values.
let age = 7
let ticket = childAges.contains(age) ? "child" : "adult"
print(ticket) // childHow Swift picks the value:
- First it evaluates the condition before
?:childAges.contains(age)istrue. - The condition is true, so the result is the value between
?and:, the string"child". - Had the condition been false, the result would have been the value after
:, the string"adult".
The ternary operator has two rules:
- the condition before
?has typeBool; - both values have the same type, or the program does not compile.
If you mix a string and a number, as in age > 5 ? "big" : 0, the compiler answers like this:
error: result values in '? :' expression have mismatching types 'String' and 'Int'Common mistake
The shop's opening hours are written as a closed range:
let hours = 9...18 // ✗
print(hours.contains(18)) // trueThe program thinks the shop is open at 18:00, when it is already closed. There is no error at compile time or at run time, only a wrong answer. When the upper bound means an end, you need ..<, as in openHours above.
Key points
a...bincludes both bounds:(3...12).contains(12)istrue.a..<bstops beforeb:(9..<18).contains(18)isfalse.containschecks whether a number falls in a range, and a range written out before.containsgoes in parentheses.condition ? a : bgivesaif the condition is true andbif not; both values have the same type.