The flip/flop (aka f/f) is a stateful operator which originates from perl.
f/f operator is implied in conditional statements (if and ternary) in ruby instead of range
so (1..5) is a range but (1..5) ? 1 : 5 is a f/f.
f/f has an internal state (true / false) and consists of two conditions.
It tuns ON(state becomes true) when it's first condition evaluates to true and OFF when it's second condition evalutes to true.
The difference between two and three dotted versions is that
two-dotted evaluates second condition immediately after the first one evaluated to true and
three-dotted doesn't.
two dotted version works like this:
A..B |
A -> false | State -> false
A -> true, B -> false | State -> true # notice how it checks both conditions
B -> false | State -> true
B -> true | State -> false
A -> false | State -> false
A -> true, B -> true | State -> false
compare it to three dotted version
A...B
A -> false | State -> false
A -> true | State -> true # three dotted version doesn't check second condition immediately
B -> false | State -> true
B -> true | State -> false
A -> false | State -> false
A -> true | State -> true
Let's follow along the amazing perl article but with examples in ruby
Two dotted example:
DATA.each_line do |line|
print "\t" if (line =~ /^start/ .. line =~ /^end/)
print line
end
__END__
First line.
start
Indented line
end
Back to left margin
This prints:
First line.
start
Indented line
end
Back to left margin
as you can see - f/f turns ON on line 2 and OFF on line 4.
Yet there is a subtlety to it. Check this out:
DATA.each_line do |line|
print "\t" if (line =~ /start/ .. line =~ /end/)
print line
end
__END__
First line.
Indent lines between the start and the end markers
Back to left margin
This prints:
First line.
Indent lines between the start and the end markers
Back to left margin
There it turns ON on line 2 and immediately turns OFF.
Let's imagine that you don't want to check the second operator immediately after the first.
It's where three-dotted f/f becomes handy. Check out the next example.
DATA.each_line do |line|
print "\t" if (line =~ /start/ ... line =~ /end/)
print line
end
__END__
First line.
Indent lines between the start and the end markers
So this is indented,
and this is the end of the indented block.
Back to left margin
Which prints:
First line.
Indent lines between the start and the end markers
So this is indented,
and this is the end of the indented block.
Back to left margin
as you can see it turns ON on line 2 and OFF on line 4
Now let's apply this to your examples
I wrote a small script to illustrate it's behaviour
def mod(n, i)
result = i % n == 0
puts "#{i} mod #{n} => #{result}"
result
end
(11..20).each { |i|
if (mod(4, i))...(mod(3, i)) # NOTE it's a three dotted version
# NOTE that those puts show previous state, not the current one!
puts true
else
puts false
end
}
two dotted result:
11 mod 4 => false
false
12 mod 4 => true
12 mod 3 => true # Notice how it checks both conditions here
true
13 mod 4 => false
false
14 mod 4 => false
false
15 mod 4 => false
false
16 mod 4 => true
16 mod 3 => false
true
17 mod 3 => false
true
18 mod 3 => true
true
19 mod 4 => false
false
20 mod 4 => true
20 mod 3 => false
true
three dotted result:
11 mod 4 => false
false
12 mod 4 => true
true
13 mod 3 => false
true
14 mod 3 => false
true
15 mod 3 => true # turns OFF here
true
16 mod 4 => true # and turns immediately ON here
true
17 mod 3 => false
true
18 mod 3 => true
true
19 mod 4 => false
false
20 mod 4 => true
true
=> 11..20
P.S. Range and flip/flop are two completely distinct operators and you should not mix them up.