Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does a Flip-Flop operator include the second condition?

Tags:

ruby

flip-flop

The following code is using a flip-flop operator.

(1..10).each {|x| print "#{x}," if x==3..x==5 }

Why are the results 3,4,5?

I think it should be 3,4.

As mentioned in a tutorial, this expression becomes true when x == 3, and continues to be true until x == 5. How could '5' been printed if it evaluates to false? Could anyone please clarify that for me?

like image 290
Huibin Zhang Avatar asked Sep 03 '13 19:09

Huibin Zhang


2 Answers

The important link, from "The Ruby Programming Language" is :

4.6.9.1 Boolean flip-flops

When the .. and ... operators are used in a conditional, such as an if statement, or in a loop, such as a while loop (see Chapter 5 for more about conditionals and loops), they do not create Range objects. Instead, they create a special kind of Boolean expression called a flip-flop. A flip-flop expression evaluates to true or false, just as comparison and equality expressions do. The extraordinarily unusual thing about a flip-flop expression, however, is that its value depends on the value of previous evalu- ations. This means that a flip-flop expression has state associated with it; it must remember information about previous evaluations. Because it has state, you would expect a flip-flop to be an object of some sort. But it isn’t—it’s a Ruby expression, and the Ruby interpreter stores the state (just a single Boolean value) it requires in its internal parsed representation of the expression.

With that background in mind, consider the flip-flop in the following code. Note that the first .. in the code creates a Range object. The second one creates the flip-flop expression:

 (1..10).each {|x| print x if x==3..x==5 }

The flip-flop consists of two Boolean expressions joined with the .. operator, in the context of a conditional or loop. A flip-flop expression is false unless and until the lefthand expression evaluates to true. Once that expression has become true, the ex- pression “flips” into a persistent true state. It remains in that state, and subsequent evaluations return true until the righthand expression evaluates to true. When that happens, the flip-flop “flops” back to a persistent false state. Subsequent evaluations of the expression return false until the lefthand expression becomes true again. In the code example, the flip-flop is evaluated repeatedly, for values of x from 1 to 10. It starts off in the false state, and evaluates to false when x is 1 and 2. When x==3, the flip-flop flips to true and returns true. It continues to return true when x is 4 and 5. When x==5, however, the flip-flop flops back to false, and returns false for the remaining values of x. The result is that this code prints 345.

like image 132
Arup Rakshit Avatar answered Oct 01 '22 19:10

Arup Rakshit


.. or flip-flop is inherited from Perl which got it from AWK and sed in *nix. It's very powerful, but in your particular use it's fairly obscure and not a good choice for the logic you want, especially in Ruby. Instead use:

(1..10).each {|x| puts x if (3..5) === x }

Which outputs:

3
4
5

That said, it's extremely powerful when you need to extract a range of lines from a file:

File.foreach('/usr/share/dict/propernames') { |li| puts li if ($. == 5 .. $. == 7) }

Which outputs:

Agatha
Ahmed
Ahmet

Perl allows an even more-terse expression using only the line numbers of the currently read line (AKA $.) but Ruby doesn't support that.

There's also the option of using regular expressions, which behave similarly as the previous comparison:

File.foreach('/usr/share/dict/propernames') { |li| puts li if (li[/^Wa/] .. li[/^We/]) }

Which outputs:

Wade
Walt
Walter
Warren
Wayne
Wendell

Because regex work, it's possible to create a complex pattern to retrieve lines from a file based on matches. As the first, then the second pattern trigger, lines are captured. If, later in the file, another line triggers the first pattern, capturing will again occur until the second pattern matches. It's wonderfully powerful:

File.foreach('/usr/share/dict/propernames') { |li| puts li if (
    li[/^Am/] .. li[/^An/] or
    li[/^Wa/] .. li[/^We/]
  )
}

Which outputs:

Amanda
Amarth
Amedeo
Ami
Amigo
Amir
Amos
Amy
Anatole
Wade
Walt
Walter
Warren
Wayne
Wendell

Or alternately, for our obscure-code speaking friends:

File.foreach('/usr/share/dict/propernames') { |li| puts li if (li[/^(?:Am|Wa)/] .. li[/^(?:An|We)/]) }
like image 31
the Tin Man Avatar answered Oct 01 '22 19:10

the Tin Man