Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ruby

flip-flop

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]
like image 946
gmuraleekrishna Avatar asked Jul 17 '14 06:07

gmuraleekrishna


2 Answers

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.

like image 75
stefkin Avatar answered Oct 18 '22 17:10

stefkin


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.

like image 28
Aleksei Matiushkin Avatar answered Oct 18 '22 15:10

Aleksei Matiushkin