146

I've just started learning Ruby and Ruby on Rails and came across validation code that uses ranges:

validates_inclusion_of :age, :in => 21..99
validates_exclusion_of :age, :in => 0...21, :message => "Sorry, you must be over 21"

At first I thought the difference was in the inclusion of endpoints, but in the API docs I looked into, it didn't seem to matter whether it was .. or ...: it always included the endpoints.

However, I did some testing in irb and it seemed to indicate that .. includes both endpoints, while ... only included the lower bound but not the upper one. Is this correct?

blackgreen
  • 34,072
  • 23
  • 111
  • 129
juil
  • 2,408
  • 3
  • 18
  • 18

5 Answers5

202

The documentation for Range says this:

Ranges constructed using .. run from the beginning to the end inclusively. Those created using ... exclude the end value.

So a..b is like a <= x <= b, whereas a...b is like a <= x < b.


Note that, while to_a on a Range of integers gives a collection of integers, a Range is not a set of values, but simply a pair of start/end values:

(1..5).include?(5)           #=> true
(1...5).include?(5)          #=> false

(1..4).include?(4.1)         #=> false
(1...5).include?(4.1)        #=> true
(1..4).to_a == (1...5).to_a  #=> true
(1..4) == (1...5)            #=> false


The docs used to not include this, instead requiring reading the Pickaxe’s section on Ranges. Thanks to @MarkAmery (see below) for noting this update.

Andrew Marshall
  • 95,083
  • 20
  • 220
  • 214
  • 14
    Better/less confusing example than the above: `(1..10).include? 10 #=> true` and `(1...10).include? 10 #=> false` – timmcliu Jul 27 '15 at 19:13
  • 1
    @timmcliu Though not relevant to illustrating the point that `(a..b) != (a...(b+1))` despite their array representations being equal (when a,b ∈ ℤ). I’ve updated my answer a bit to expand on that. – Andrew Marshall Jul 27 '15 at 23:30
  • If Range is not a set of values, then why does this piece of code treat Range as a set of values: (1..5).inject {|sum, n| sum + n } – VaVa Oct 25 '15 at 15:36
  • 2
    @ValentinVassilev Range isn’t a set of values, but it can generate them. `inject` comes from [`Enumerable`](http://ruby-doc.org/core/Enumerable.html) which `Range` includes; `Enumerable` utilizes `#each`, which [`Range` implements](http://ruby-doc.org/core/Range.html#method-i-each). The list generated by `Range#each` is never contained within the `Range` object itself. – Andrew Marshall Oct 25 '15 at 16:14
  • Oof, just the opposite of Swift, D, Rust, C#... ‍♀️. Swift uses `...` for inclusive ranges, Rust uses `..=`, and they all use `..` (except Swift which later changed to `..<`) for half open/end exclusive ranges. – Dwayne Robinson Feb 12 '21 at 00:56
8

That is correct.

1.9.3p0 :005 > (1...10).to_a
 => [1, 2, 3, 4, 5, 6, 7, 8, 9]
1.9.3p0 :006 > (1..10).to_a
 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

The triple-dot syntax is less common, but is nicer than (1..10-1).to_a

Chris Heald
  • 61,439
  • 10
  • 123
  • 137
  • 14
    I think it's really bizarre that *more* dots means the range represents *fewer* values. I guess it's just that `..` is more common and thus less is preferred for it? – Andrew Marshall Mar 13 '12 at 19:43
  • 2
    @Andrew: I thought that too, but maybe it's down to the two-dot variety being more commonly desired and thus shorter to type? – safetycopy Mar 13 '12 at 20:02
  • 1
    Also, note that `(a..b-1) != (a...b)`, though this answer implies they are. – Andrew Marshall Jul 27 '15 at 23:30
  • 1
    (a..b-1) == (a...b) only in the case where a and b are integers and you enumerate the ranges into arrays. Consider the range (1.0...3.5) - what is the value just before 3.5? Certainly not 2.5! – Chris Heald Jul 27 '15 at 23:32
7

The API docs now describe this behaviour:

Ranges constructed using .. run from the beginning to the end inclusively. Those created using ... exclude the end value.

-- http://ruby-doc.org/core-2.1.3/Range.html

In other words:

2.1.3 :001 > ('a'...'d').to_a
 => ["a", "b", "c"] 
2.1.3 :002 > ('a'..'d').to_a
 => ["a", "b", "c", "d"] 
Community
  • 1
  • 1
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
2

a...b excludes the end value, while a..b includes the end value.

When working with integers, a...b behaves as a..b-1.

>> (-1...3).to_a
=> [-1, 0, 1, 2]

>> (-1..2).to_a
=> [-1, 0, 1, 2]

>> (-1..2).to_a == (-1...3).to_a
=> true

But really the ranges differ on a real number line.

>> (-1..2) == (-1...3)
=> false

You can see this when incrementing in fractional steps.

>> (-1..2).step(0.5).to_a
=> [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]

>> (-1...3).step(0.5).to_a
=> [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0, 2.5]
Dennis
  • 56,821
  • 26
  • 143
  • 139
  • 1
    Still incorrect after edit. Even if `a` & `b` are integers, the ranges are different. Only when each is converted to an array are they the same. A specific counterexample exists in the accepted answer. – Andrew Marshall Jul 10 '15 at 12:36
  • 2
    @AndrewMarshall What I meant to say with that example (but not very well evidently) is on an integer scale it behaves that way. This isn't the case on a more precise fractional scale, as pointed out in your answer. I reckon ranges are most often used on an integer scale though, which is why I believe such an explanation is helpful. – Dennis Jul 11 '15 at 17:05
-5

.. and ... denote a range.

Just see it in irb:

ruby-1.9.2-p290 :032 > (1...2).each do puts "p" end
p
 => 1...2 
ruby-1.9.2-p290 :033 > (1..2).each do puts "p" end
p
p
daniel
  • 9,732
  • 7
  • 42
  • 57