213

Is it possible to use the range operator ... and ..< with if statement. Maye something like this:

let statusCode = 204
if statusCode in 200 ..< 299 {
  NSLog("Success")
}
Jimmy
  • 10,427
  • 18
  • 67
  • 122

6 Answers6

469

You can use the "pattern-match" operator ~=:

if 200 ... 299 ~= statusCode {
    print("success")
}

Or a switch-statement with an expression pattern (which uses the pattern-match operator internally):

switch statusCode {
case 200 ... 299:
    print("success")
default:
    print("failure")
}

Note that ..< denotes a range that omits the upper value, so you probably want 200 ... 299 or 200 ..< 300.

Additional information: When the above code is compiled in Xcode 6.3 with optimizations switch on, then for the test

if 200 ... 299 ~= statusCode

actually no function call is generated at all, only three assembly instruction:

addq    $-200, %rdi
cmpq    $99, %rdi
ja  LBB0_1

this is exactly the same assembly code that is generated for

if statusCode >= 200 && statusCode <= 299

You can verify that with

xcrun -sdk macosx swiftc -O -emit-assembly main.swift

As of Swift 2, this can be written as

if case 200 ... 299 = statusCode {
    print("success")
}

using the newly introduced pattern-matching for if-statements. See also Swift 2 - Pattern matching in "if".

Ahmad F
  • 30,560
  • 17
  • 97
  • 143
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    Cool, is this O(1)? Also, it'd be nice if Swift had a short hand for switch statements, like Scala for example. But given that you're always forced to handle all possibilities at compile time in Swift, it may not really be feasible. – Sky Jul 22 '14 at 17:07
  • 2
    @Sky: From the generated assembly code one can see that a library function `func ~= (Range, A) -> Bool` is called. I would *assume* that this function works with O(1). – Martin R Jul 22 '14 at 17:43
  • 4
    @Downvoter: Some explaining comment would be nice, so that I can improve or fix the answer ... – Martin R Jul 22 '14 at 17:50
  • 1
    @MartinR how you get to know which function called by assembly language.Hopper? +1 for cool answer – codester Jul 22 '14 at 17:55
  • 3
    @codester: I compiled the code on the command line with `xcrun -sdk macosx swift -emit-assembly main.swift` and inspected the assembly code. Then I used `xcrun swift-demangle ...` to de-mangle the name of the called function. - Unfortunately, Xcode cannot yet create assembly code for Swift files, perhaps it will work in a later version. – Martin R Jul 22 '14 at 17:58
  • Martin, where is the "~=" pattern match operator documented? I looked in the Swift Standard Library PDF and there's no mention. I see it referenced in the "Swift Programming Language" iBook, but not defined. – Duncan C Apr 04 '15 at 14:21
  • 1
    @DuncanC: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html, section "Expression Pattern": *"... By default, the ~= operator compares two values of the same type using the == operator. It can also match an integer value with a range of integers in an Range object, ..."* – Martin R Apr 04 '15 at 14:37
  • That's a pretty oblique reference to the pattern-match operator. I was hoping for specific documentation on that operator. – Duncan C Apr 04 '15 at 16:15
  • I don't know if this is a recent change or not, but ranges can't take spaces. So `200 ... 299` results in a compiler error. But `200...299` Does not. I tried to submit an edit, but it was complaining about formatting code blocks for some reason even though all I did was delete the spaces... :/ – TaylorAllred Mar 28 '16 at 23:14
  • @TaylorAllred: `if case 200 ... 299 = statusCode { ... }` (with spaces) still compiles without problems in my Xcode 7.3. – Martin R Mar 29 '16 at 08:35
  • Weird, OK, I'll do some more testing on my end, thanks for responding – TaylorAllred Mar 29 '16 at 15:02
  • Stylistically, I write ranges without spaces, as does [Apple's Swift documentation](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID73). – ma11hew28 May 12 '16 at 13:48
  • How do I do the inverse? Like: `if case 200...299 != statusCode` or `if 200...299 ~! statusCode` – ma11hew28 May 12 '16 at 13:56
  • @mattdipasquale: Apart from the obvious `if case 200 ... 299 = statusCode { } else { ... }` or using `guard` there is – as far as I know – no negation of matching operators. `if (200...299).contains(statusCode)` works but that is not pattern matching. – Martin R May 12 '16 at 14:16
  • @mattdipasquale: ... I meant `if !(200...299).contains(statusCode)` – Martin R May 12 '16 at 14:34
108

This version seems to be more readable than pattern matching:

if (200 ... 299).contains(statusCode) {
    print("Success")
}
Serhii Yakovenko
  • 12,424
  • 2
  • 27
  • 26
16

This is an old thread, but it seems to me we're over-thinking this. It seems to me the best answer is just

if statusCode >= 200 && statusCode <= 299

There's no

if 200 > statusCode > 299

form that I'm aware of, and the other suggested solutions are doing function calls, which are harder to read, and might be slower to execute. The pattern match method is a useful trick to know, but seems like a poor fit for this problem.

Edit:

Personally, I find the pattern match operator to be hideous, and wish the compiler would support if x in 1...100 syntax. That is sooooo much more intuitive and easy to read than if 1...100 ~= x

Community
  • 1
  • 1
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1
    You are right that this version is better to read, I just tried to answer the explicit question *"Is it possible to use the range operator ... ?"* – **But** Xcode 6.3 beta (in optimized mode) generates exactly three assembly instructions for `if 200 ... 299 ~= statusCode`, no function call :) – Martin R Apr 04 '15 at 14:58
  • 14
    Actually `if 200 ... 299 ~= statusCode` gives the *same* assembly code as `if statusCode >= 200 && statusCode <= 299` – Martin R Apr 04 '15 at 15:06
  • @MartinR, that's impressive smarts from the compiler! Gotta love LLVM. – Duncan C Apr 04 '15 at 16:03
  • 7
    Unless this conditional is in a critical section that gets visited thousands of times per second, worrying about function call overhead is premature optimization. Even then, I'd worry more about what a function call is *doing* rather than the cost of calling it. Nice job @MartinR for proving there's no cost regardless, though. – rickster Apr 04 '15 at 16:04
  • 1
    @rickster, true enough. I still tend to prefer efficient constructs over inefficient ones as a matter of habit (assuming readability is similar). Not to the extent that I waste too much of MY time on it, but it still pays to know what the costs of different approaches are. – Duncan C Apr 04 '15 at 16:52
  • 1
    This is nitpicking, but I disagree with your suggestion that your if statement is more readable or understandable than the answer posted by @SerhiiYakovenko. Simply on the basis of DRY - you name "statusCode" twice. In a late-night bleary-eyed debugging session after I'd decided that a different variable named "statusValue" should be used here instead of "statusCode", I just might make the mistake of changing one of the variable names and not the other. – RenniePet Dec 28 '16 at 06:27
  • @Duncan C - I agree: given two more-or-less equal choices, write the one that executes faster. – rghome Feb 15 '17 at 22:13
4

I wanted to check 4xx errors except 401. Here is the code:

let i = 401

if 400..<500 ~= i, i != 401 {
    print("yes")
} else {
    print("NO")
}
abhimuralidharan
  • 5,752
  • 5
  • 46
  • 70
1

I preferred Range .contains() operator too, until found that its implementation is inefficient - https://oleb.net/blog/2015/09/swift-ranges-and-intervals/

We can represent the condition x < 0 using a range: (Int.min..<0).contains(x) is exactly equivalent. It is vastly slower, though. The default implementation of contains(_:) traverses the entire collection, and executing a loop nine quintillion times in the worst case is not cheap.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Entro
  • 119
  • 2
  • 5
  • 1
    This information is obsolete, and wrong. It already was wrong at the time you wrote your answer. – KPM Jul 27 '20 at 21:21
1

if you want to know if the response status code is a success, simply make it this way

  if response.statuscode < 300 {
      print("response is a success")
  }

if you iterate from 200,201,203,204 ... 300. There were just too much unnecessary iterations. Hope this helps! :D