Difference between 3-dot-range operator and 2-dot-range operator in flip flop ruby
Asked Answered
M

4

5

Please help me to understand the difference between range operators ... and .. as "flip-flops" used in Ruby.

This is an example from Pragmatic Programmers guide to Ruby:

a = (11..20).collect {|i| (i%4 == 0)..(i%3 == 0) ? i : nil}

which returns:

[nil, 12, nil, nil, nil, 16, 17, 18, nil, 20]

Also:

a = (11..20).collect {|i| (i%4 == 0)...(i%3 == 0) ? i : nil}

returned:

[nil, 12, 13, 14, 15, 16, 17, 18, nil, 20]
Monthly answered 17/7, 2014 at 6:58 Comment(1)
possible duplicate of Difference between '..' (double-dot) and '...' (triple-dot) in range generation?Swords
P
0

The difference between 2 dots and 3 dots in Ruby is inclusion. For example

(1..100)
=> All numbers starting from 1 and ending at 100 INCLUDING 100

(1...100)
=> All numbers starting from 1 that are less than 100

(1..100).include?(100)
=> true

(1...100).include?(100)
=> false

Hope this helps.

Pseudaxis answered 17/7, 2014 at 15:19 Comment(0)
B
4

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.

Bowerbird answered 3/8, 2018 at 20:4 Comment(0)
D
3

The concept of flip-flop switch actually came from electronics. The main advantage of it is that it remembers it’s state. Consider flip-flop boolean range as a variable, storing boolean value. Let’s take a look at following example:

1.upto(10).each do |i|
  puts i if (i%2==0)..(i%4==0)
end

        #                        vvvv   value of “hidden” state
2       # left range boundary  ⇨ true
3
4       # right range boundary ⇨ false, no 5 follows 
6       # left range boundary  ⇨ true
7
8       # right range boundary ⇨ false, no 9 follows 
10

In your first example the “condition” became true for 4-multiples and turns back to false on 3-multiples. In the second example the condition variable was not turned off on 12 just because the right range boundary (i%3) is excluded, since it’s a 3-dot-range.

Hope the example was not too tangled.

Delamination answered 17/7, 2014 at 8:44 Comment(1)
Yes. It is there, but I never have seen the use of flip flop in any codes till now I have seen.Haveman
M
0

When a double dot is used in a range it creates a range of numbers which go up to and includes the maximum number passed in. When a triple dot is used it creates a range which goes up to but does NOT include the maximum number passed in.

So:

 (1..5).each {| i | puts i} #will print 1,2,3,4,5

While:

(1...5).each {| i | puts i} #will print 1,2,3,4
Mozarab answered 17/7, 2014 at 7:40 Comment(1)
But using dot operators as flip flops, it will be having different behaviour. the operands are not iterable (i%4 == 0)..(i%3 == 0).Monthly
P
0

The difference between 2 dots and 3 dots in Ruby is inclusion. For example

(1..100)
=> All numbers starting from 1 and ending at 100 INCLUDING 100

(1...100)
=> All numbers starting from 1 that are less than 100

(1..100).include?(100)
=> true

(1...100).include?(100)
=> false

Hope this helps.

Pseudaxis answered 17/7, 2014 at 15:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.