What Are "Intervals" in Swift Ranges

What are intervals in swift ranges?

As of Swift 3 (with Xcode 8), the Interval types are no more. Now the family of Range<T> types include the functionality of both the former range and interval types, and additionally conform to the new model for collection types and indices.


In Swift 2.x and older... Ranges are for iterating, and Intervals are for pattern matching.

func testNum(num: Int) {
let interval: HalfOpenInterval = 0..<10
let range = 10..<20
switch num {
case interval: // this works
break
case range: // error "does not conform to protocol IntervalType"
break
default:
break
}
}

A Range type is optimized for generating values that increment through the range, and works with types that can be counted and incremented.

An Interval type is optimized for testing whether a given value lies within the interval. It works with types that don't necessarily need a notion of incrementing, and provides operations like clamping one range to another (e.g. (0..<10).clamp(5..<15) yields 5..<10) that are useful for complex pattern matching.

Because the ..< and ... operators have two forms each--one that returns a Range and one that returns an Interval--type inference automatically uses the right one based on context. So, if you write 0..<10 in a case label of a switch statement, Swift automatically constructs a HalfOpenInterval because a switch statement requires an Interval type.

The ~= operator is a way to do one test on an interval without a switch statement. Writing interval ~= value is equivalent to interval.contains(value).


It's worth noting that you can find out many of these things by looking at the standard library interface and its comments: write a type name like HalfOpenInterval in a playground, then command-click to jump to its definition.

Switch case with range

This should work.

private func calculateUserScore() -> Int {
let diff = abs(randomNumber - Int(bullsEyeSlider.value))
switch diff {
case 0:
return PointsAward.bullseye.rawValue
case 1..<10:
return PointsAward.almostBullseye.rawValue
case 10..<30:
return PointsAward.close.rawValue
default:
return 0
}
}

It's there in the The Swift Programming Language book under Control Flow -> Interval Matching.

How does one iterate over an Array of Ranges?

The problem is not that you have an array of ranges, but that
ClosedRange in Swift 3 represents

An interval over a comparable type, from a lower bound up to, and including, an upper bound.

For example, a closed range can be used with Double

let r: ClosedRange<Double> = 1.1...2.2

where enumerating all possible values does not make much sense.

What you need is CountableClosedRange which is

A closed range that forms a collection of consecutive values.

and in particular is a collection and can be iterated over:

let a = 0
let b = 10
var arr = [CountableClosedRange<Int>]()
let myRange: CountableClosedRange = a...b
arr.append(myRange)

for each in arr {
for every in each {
print(every)
}
}

You can just write

let myRange = a...b

since by default, the ... operator produces a CountableClosedRange
if its operands are Strideable.

Similarly there is Range and CountableRange for half-open ranges.
For more information, see Range Types in SE-0065 A New Model for Collections and Indices.

How do string intervals work?

As I understand it, ClosedInterval(lo, hi) represents all strings s such that

lo <= s <= hi

and HalfOpenInterval(lo, hi) represents all strings s such that

lo <= s < hi

where <= and < are determined by the lexicographic ordering of the Strings,
i.e. be comparing the characters one by one, until a difference is found.

For example "QED" < "T" < "WTF" because Q < T < W, but "F" < "QED" because
F < Q. Therefore

HalfOpenInterval("QED" , "WTF").contains("T") == true
HalfOpenInterval("QED" , "WTF").contains("F") == false

And "QED" < "QSF" because Q == Q and E < S, but "QAF" < "QED" because Q == Q and A < E. Therefore

HalfOpenInterval("QED" , "WTF").contains("QSF") == true
HalfOpenInterval("QED" , "WTF").contains("QAF") == false

That should explain all your test results.

Finally,

let clampedString0 = aThroughHHalfOpen.clamp ( tThroughXHalfOpen )

is an empty interval because "A"..<"H" and "T"..<"X" have no points in common.
An empty interval can be represented by HalfOpenInterval(dummy, dummy) for any
value of dummy.

How to determine when multiple(n) datetime ranges overlap each other

You need to do the following steps:

  1. Convert your date string to Date objects.
  2. Create DateIntervals with your start and end date objects.
  3. Loop through the intervals and check for intersection.

Here is a quick code I can come up with in swift:

func answer()  {
let dateFormat = "yyyy-MM-dd HH:mm Z"
// Date ranges
let times = [["start": "2016-01-01 12:00 +0000", "end": "2016-05-01 03:00 +0000"],
["start": "2016-01-01 03:00 +0000", "end": "2016-05-01 03:00 +0000"],
["start": "2016-01-01 03:00 +0000", "end": "2016-04-30 13:31 +0000"]]

var intervals = [DateInterval]()
// Loop through date ranges to convert them to date intervals
for item in times {
if let start = convertStringToDate(string: item["start"]!, withFormat: dateFormat),
let end = convertStringToDate(string: item["end"]!, withFormat: dateFormat) {
intervals.append(DateInterval(start: start, end: end))
}
}

// Check for intersection
let intersection = intersect(intervals: intervals)
print(intersection)
}

// Converts the string to date with given format
func convertStringToDate(string: String, withFormat format: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.date(from: string)
}

// Cehck for intersection among the intervals in the given array and return
// the interval if found.
func intersect(intervals: [DateInterval]) -> DateInterval? {
// Algorithm:
// We will compare first two intervals.
// If an intersection is found, we will save the resultant interval
// and compare it with the next interval in the array.
// If no intersection is found at any iteration
// it means the intervals in the array are disjoint. Break the loop and return nil
// Otherwise return the last intersection.

var previous = intervals.first
for (index, element) in intervals.enumerated() {
if index == 0 {
continue
}

previous = previous?.intersection(with: element)

if previous == nil {
break
}
}

return previous
}

Note: Please test with several examples. I tested with above date ranges and its working fine.

What is the most effective way to iterate over a date range in Swift?

If I understand your question correctly, the user will check off some weekdays and provide a duration as a number of days.

Assuming you have the selected weekdays in an array and the duration, you can get the list of matching dates as follows:

// User selected weekdays (1 = Sunday, 7 = Saturday)
var selectedWeekdays = [2, 4, 6] // Example - Mon, Wed, Fri
var duration = 10 // Example - 10 days

let calendar = Calendar.current
var today = Date()
let dateEnding = calendar.date(byAdding: .day, value: duration, to: today)!

var matchingDates = [Date]()
// Finding matching dates at midnight - adjust as needed
let components = DateComponents(hour: 0, minute: 0, second: 0) // midnight
calendar.enumerateDates(startingAfter: today, matching: components, matchingPolicy: .nextTime) { (date, strict, stop) in
if let date = date {
if date <= dateEnding {
let weekDay = calendar.component(.weekday, from: date)
print(date, weekDay)
if selectedWeekdays.contains(weekDay) {
matchingDates.append(date)
}
} else {
stop = true
}
}
}

print("Matching dates = \(matchingDates)")

Swift Range behaving like NSRange and not including endIndex

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.

swift range greater than lower bound

nextUp from the FloatingPoint protocol

You can make use of the nextUp property of Double, as blueprinted in the FloatingPoint protocol to which Double conforms

nextUp

The least representable value that compares greater than this value.

For any finite value x, x.nextUp is greater than x. ...

I.e.:

let uptoTwo = 0.0...2.0
let twoPlus = 2.0.nextUp...4.0

The property ulp, also blueprinted in the FloatingPoint protocol, has been mentioned in the comments to your question. For most numbers, this is the difference between self and the next greater representable number:

ulp

The unit in the last place of self.

This is the unit of the least significant digit in the significand of
self. For most numbers x, this is the difference between x and
the next greater (in magnitude) representable number. ...

nextUp does, in essence, return the value of self with the addition of ulp. So for your example above, the following is equivalent (whereas, imo, nextup should be preferred in this use case).

let uptoTwo = 0.0...2.0 
let twoPlus = (2.0+2.0.ulp)...4.0

You might also want to consider replacing the lower bound literal in twoPlus with the upperBound property of the preceding uptoTwo range:

let uptoTwo = 0.0...2.0                       // [0, 2] closed-closed
let twoPlus = uptoTwo.upperBound.nextUp...4.0 // (2, 4] open-closed

if uptoTwo.overlaps(twoPlus) {
print("the two ranges overlap ...")
}
else {
print("ranges are non-overlapping, ok!")
}
// ranges are non-overlapping, ok!

Swift range check in switch

It is not possible to create a range in Swift that is open on the low end.

This will accomplish what you intended:

switch value {
case 0...f1:
print("one")
case f1...(f1 + f2):
print("two")
case (f1 + f2)...1:
print("three")
default:
print("four")
}

Since switch matches the first case it finds, you don't have to worry about being non-inclusive on the low end. f1 will get matched by the "one" case, so it doesn't matter that the "two" case also includes it.

If you wanted to make the first case exclude 0, you could write it like this:

case let x where 0...f1 ~= x && x != 0:

or

case let x where x > 0 && x <= f1:


Related Topics



Leave a reply



Submit