Why does a Flip-Flop operator include the second c

2019-04-08 07:16发布

问题:

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?

回答1:

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.



回答2:

.. 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)/]) }


回答3:

I find a piece of code to illustrate how the flip-flop works (just in the same book where this piece of code appears, hope it helps for those having same question like me)

$state = false # Global storage for flip-flop state
    def flipflop(x) # Test value of x against flip-flop
        if !$state # If saved state is false
            result = (x == 3) # Result is value of lefthand operand
            if result # If that result is true
                 $state = !(x == 5) # Then saved state is not of the righthand operand
            end
            result # Return result
        else # Otherwise, if saved state is true
            $state = !(x == 5) # Then save the inverse of the righthand operand
            true # And return true without testing lefthand
        end
    end


回答4:

Are you looking for an exclusive range? You can use three dots and the cover? method.

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

The reason it prints 3,4,5, in your example is because it says if x is in the range from 3 to 5 print it.



回答5:

To clarify the comment by @MurifoX, The flip-flop is true until x==5, and thus true specifically when x==5, but is false every time the expression is evaluated after that. Thus you are still seeing 5 being printed.



回答6:

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 evaluations. 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 expression “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 .