swift range greater than lower bound
Asked Answered
O

4

13

I need to implement experience filter like this

  • 0 to 2 years
  • 2+ to 4 years

How to express it in swift range?

Problem is I can't express more than 2 to 4 years. While I can do less than upper bounds. e.g. like this

let underTen = 0.0..<10.0

I need something like this (greater than lower bound)

let uptoTwo = 0.0...2.0
let twoPlus = 2.0>..4.0  // compiler error

Currently I am doing

let twoPlus = 2.1...4.0

But this is not perfect.

Onslaught answered 3/1, 2017 at 6:43 Comment(19)
Are you working with Ints?Pencel
@Pencel edit: No. experience could be fraction in yearsOnslaught
Do you use arrays? You can try to use filter for sortingCarboy
is (2+1)..4 work for you?Swordsman
@ArtemNovichkov plz explainOnslaught
As I undestand, you need to use [0, 2) and (2, 4] ranges, so 2 years experience doesn't include any range, right?Carboy
@WarifAkhandRishi Your question and your example contradict each other. Your question asks to segment make ranges for "0 to 2" (2 is included) and "2+ to 4), but your example shows "0 up to, not including 2" and "2+ to 4Pencel
@Swordsman no it could be fraction in yearsOnslaught
@ArtemNovichkov I need to use [0,2] and [2+,4] ranges. 2 years experience included in first range (0,2) not in 2nd range (2+,4)Onslaught
How do you use the two ranges? If you use 0.0...2.0 and 2.0...4.0 then it will work as desired if you check the first range and then the second range. If the value is 2.0 it will be caught by the first range. This means only values over 2.0 will be caught by the second range.Immense
@Immense :) like it. didn't come to my mind. but sill it depends on my checking sequence. Is there any better solution to this?Onslaught
You need to use a switchUnsling
Warif: rather than adding 0.1, add Double.ulp it's as close to 2.0 as you can possibly go, without being at 2.0Pencel
switch years { case 0...2: print(true) case 2...4: print(true) }Unsling
It will print true only at the first case if years is equal to 2.0Unsling
@Pencel liked the idea but Double.ulp shows compiler error.Onslaught
@Warif look up the documentation, it was something like that, I don't remember and I can't check right nowPencel
Just add 2.0.ulpUnsling
@LeoDabus thanks :)Onslaught
W
29

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!
War answered 3/1, 2017 at 9:35 Comment(2)
@sketchyTech I appreciate the edit, but there a (semantic) issue with defining a new custom operator such as this one. In Swift 3, the Range type is explicitly described as one defining a half-open interval (using e.g. the half-open operator ..<) from a lower bound and not including, an upper bound. On the other hand, the ClosedRange is descried as a closed interval including its lower as well as its upper bound. Your operator above construct a ClosedRange instance, but may be confusing as it does not include the lower bound given when constructing the closed interval, ...War
... contrary to what we might expect when instantiation an instance of the ClosedRange type. If we were to really add a new half-open operator (excluding the lower bound), we should, for semantics, also create a new Range type, covering this special half-open case (which we don't really like to do, though). Consequently, I will revert your edit (this time; still much appreciated, many/most of my answers can always be improved!), but feel free to add your own answer using the custom operator.War
C
3

Rather than create a new type of range you can instead create a method that will identify values above the lower bound:

extension ClosedRange {
    func containsAboveLowerBound(value:Bound) -> Bool {
        if value > self.lowerBound {
            return self.contains(value)
        }
        else {
            return false
        }
    }
}

implementing it like so:

let r = 2.0...3.0
r.containsAboveLowerBound(value: 2.0) // false
r.containsAboveLowerBound(value: 2.01) // true
Creamy answered 3/1, 2017 at 8:47 Comment(1)
valid answer +1. looking for simpler solution.Onslaught
S
2

If your actual purpose is to use ranges for filtering, how about making them as closures?

let underTen = {0.0 <= $0 && $0 < 10.0}
let upToTwo = {0.0 <= $0 && $0 <= 2.0}
let twoPlus = {2.0 <  $0 && $0 <= 4.0}

You can use such filtering closures like this:

class Client: CustomStringConvertible {
    var experience: Double
    init(experience: Double) {self.experience = experience}
    var description: String {return "Client(\(experience))"}
}
let clients = [Client(experience: 1.0),Client(experience: 2.0),Client(experience: 3.0)]
let filteredUnderTen = clients.filter {underTen($0.experience)}
print(filteredUnderTen) //->[Client(1.0), Client(2.0), Client(3.0)]
let filteredUpToTwo = clients.filter {upToTwo($0.experience)}
print(filteredUpToTwo) //->[Client(1.0), Client(2.0)]
let filteredTwoPlus = clients.filter {twoPlus($0.experience)}
print(filteredTwoPlus) //->[Client(3.0)]
Sommelier answered 3/1, 2017 at 7:42 Comment(1)
Im not using it for this problem. But i'll use it in future for other type of situations. Showed me something new thanks.Onslaught
S
0

I think this does it,

extension ClosedRange where Bound == Int {
    func containsExclusiveOfBounds(_ bound: Bound) -> Bool {
        return !(bound == lowerBound || bound == upperBound)
    }
}
Salley answered 28/12, 2020 at 22:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.