Difference between '..' (double-dot) and '...' (triple-dot) in range generation?
Asked Answered
R

5

150

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?

Roswald answered 13/3, 2012 at 19:35 Comment(0)
I
207

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.

Ionopause answered 13/3, 2012 at 19:48 Comment(5)
Better/less confusing example than the above: (1..10).include? 10 #=> true and (1...10).include? 10 #=> falseDisagree
@Disagree 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.Ionopause
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 }Ewen
@ValentinVassilev Range isn’t a set of values, but it can generate them. inject comes from Enumerable which Range includes; Enumerable utilizes #each, which Range implements. The list generated by Range#each is never contained within the Range object itself.Ionopause
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.Snoddy
M
9

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

Microbicide answered 13/3, 2012 at 19:38 Comment(4)
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?Ionopause
@Andrew: I thought that too, but maybe it's down to the two-dot variety being more commonly desired and thus shorter to type?Potshot
Also, note that (a..b-1) != (a...b), though this answer implies they are.Ionopause
(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!Microbicide
M
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"] 
Marinate answered 8/10, 2014 at 22:50 Comment(0)
L
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]
Lakesha answered 15/1, 2015 at 11:19 Comment(2)
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.Ionopause
@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.Lakesha
C
-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
Chamber answered 13/3, 2012 at 19:41 Comment(1)
Doesn't really answer the question, though; both are described to be ranges. Inclusive vs Exclusive range.Schertz

© 2022 - 2024 — McMap. All rights reserved.