No difference between .. and ... flip/flop operators?

M

Michael campbell

http://sean.chittenden.org/programming/ruby/programming_ruby/
language.html

Search for "Ranges in Boolean Expressions" (this was missing from the
online pickaxe at rubycentral.com - not sure why).

If someone can explain the difference in a meaningful way, I'd be
grateful... I have enough trouble with the flip-flop as it is, and now
this!

The 2 dot form evaluates the end condition at the same time as the beginning one, so will flip to "on" then "off" if both are true. The 3 dot form does not.
 
D

Dan Doel

Ah, I see.

Well, I can explain it.

When you use "expr1..expr2", if expr1 and expr2 are both true for some
element,
then the expression evaluates to true for just that element. That is, it
gets turned
on, evaluates the code, and then turns it off. In other words, it's
doing this in the
internal state:

within_range = false
collection.each do |l|
within_range = true if expr1
#do stuff
within_range = false if expr2
end

When you use 3 dots, it seems you can either turn it on or turn it off,
but not
both on an iteration (although it only talks about turning it on). So
it's doing
something more like this:

within_range = false

collection.each do |l|
if within_range || expr1
#do stuff
end

if !within_range
within_range = true if expr1
else
within_range = false if expr2
end
end

Or something like that. I may not have gotten the semantics quite right.

I must say, this seems completely unrelated to .. and ... in the context of
ranges.

- Dan
 
P

Phil Tomson

Actually there is a difference... see:


http://sean.chittenden.org/programming/ruby/programming_ruby/
language.html

Search for "Ranges in Boolean Expressions" (this was missing from the
online pickaxe at rubycentral.com - not sure why).

If someone can explain the difference in a meaningful way, I'd be
grateful... I have enough trouble with the flip-flop as it is, and now
this!


Nathaniel


Thanks for the reference. I thought I'd seen that state diagram
somewhere. It wasn't in the online version I was checking.

The best way to illustrate the difference is to use an example. Given the
file 'file':

nine
10 BEGIN skdadk END
eleven
12
thirteen
14 END PATTERN
fifteen
---------END OF FILE-------------

And given the script:

File.foreach("filename"){|l|
if l=~/BEGIN/ .. l=~/END/
puts l
end
}

The output will be:

10 BEGIN skdadk END

changing the .. to ... , the output will be:

10 BEGIN skdadk END
eleven
12
thirteen
14 END PATTERN


I think this is different behavior from Perl's version of the ...
flip/flop, but I'ld have to check.

Phil
 
N

Nathaniel Talbott

I'm open to that. As I said, so far, the people who have disliked the
operator have only provided solutions to the stated examples of its
use, not a general solution replacement.

Here's my stab at a replacement. My goal with it was to replace the
syntax with a library. If it doesn't solve the general case, please
improve the tests, and I'll do another iteration. I still wonder if
there isn't a prettier solution to this problem in general, but this,
at least, is a start:

module BooleanRange
def flip(name, immediate=true)
raise ArgumentError, "No block" unless(block_given?)
@_flippers ||= Hash.new(false)
state = @_flippers[name]
start, finish = yield
value = state
if(state)
if(finish)
state = false
value = true
end
else
if(start)
state = !(finish && immediate)
value = true
end
end
@_flippers[name] = state
value
end

def flip2(name, &test)
flip(name, false, &test)
end
end

require 'test/unit'

class TestBooleanRanges < Test::Unit::TestCase
include BooleanRange

def test_syntax
assert_equal([nil, 12, nil, nil, nil, 16, 17, 18, nil, 20],
(11..20).collect{|i| (i%4 == 0)..(i%3 == 0) ? i : nil})
assert_equal([nil, 12, 13, 14, 15, 16, 17, 18, nil, 20],
(11..20).collect{|i| (i%4 == 0)...(i%3 == 0) ? i : nil})
end

def test_extension
assert_equal([nil, 12, nil, nil, nil, 16, 17, 18, nil, 20],
(11..20).collect{|i| flip:)a){[i%4 == 0, i%3 == 0]} ? i : nil})
assert_equal([nil, 12, 13, 14, 15, 16, 17, 18, nil, 20],
(11..20).collect{|i| flip2:)b){[i%4 == 0, i%3 == 0]} ? i : nil})
end

def test_nesting
a = []
b = []
"abc { def [ ghi jkl ] mno } pqr".split(/ /).each do |e|
if(flip:)a){[/\{/ =~ e, /\}/ =~ e]})
if(flip:)b){[/\[/ =~ e, /\]/ =~ e]})
b << e unless(e =~ /\[|\]/)
else
a << e unless(e =~ /\{|\}/)
end
end
end

assert_equal(["def", "mno"], a)
assert_equal(["ghi", "jkl"], b)
end
end


It feels like to me that there's a simple, clear and brilliant approach
hiding under this need, but I sure can't see it at this point. If you
can, please chime in :)


Nathaniel

<:((><
 
M

Michael campbell

Nathaniel Talbott wrote:

It feels like to me that there's a simple, clear and brilliant approach
hiding under this need, but I sure can't see it at this point. If you
can, please chime in :)

Yes, it's the ".." operator... >=)
 
A

Avi Bryant

And just what would you replace that functionality with? It seems to me
that it would take several lines of code to replace the following
functionality:

f.each_line {|l|
if l=~/BEGIN/ .. l=~/END/
#do something in this range of lines
end
}

What an odd operator! It took me a bit of toying around to figure out
exactly what it does (I didn't know what the scope of its state
variable was, but evidently it's the enclosing method).

Anyway, it seems to me you could pretty easily replace this with a
simple flipflop class:

ff = FlipFlop.new
f.each_line {|l|
ff.from(l=~/BEGIN/, l=~/END/) do
#do something in this range of lines
end
}

class FlipFlop
def initialize
@in_range = false
end

def from(start_cond, end_cond)
@in_range = true if start_cond
yield
@in_range = false if end_cond
end
end

Seems a lot clearer than a special operator. But maybe that's just my
Smalltalker's minimalism coming out...

Avi
 
A

Avi Bryant

I wrote:

def from(start_cond, end_cond)
@in_range = true if start_cond
yield
@in_range = false if end_cond
end

Ack. That should be "yield if @in_range", of course.
 
P

Phil Tomson

I wrote:

def from(start_cond, end_cond)
@in_range = true if start_cond
yield
@in_range = false if end_cond
end

Ack. That should be "yield if @in_range", of course.


Seems to work and be nestable, as in the usage here:

ff = FlipFlop.new
ff2= FlipFlop.new
f = File.open('filename')
f.each_line {|l|
ff.from(l=~/BEGIN/, l=~/END/) do
#inside the BEGIN .. END block
ff2.from(l=~/def/,l=~/end/) do
#inside def .. end block
puts l
end
end
}

But it's more cumbersome having to predeclare all the FlipFlops.

Phil
 
M

Michael campbell

Phil said:
Seems to work and be nestable, as in the usage here:

ff = FlipFlop.new
ff2= FlipFlop.new
f = File.open('filename')
f.each_line {|l|
ff.from(l=~/BEGIN/, l=~/END/) do
#inside the BEGIN .. END block
ff2.from(l=~/def/,l=~/end/) do
#inside def .. end block
puts l
end
end
}

But it's more cumbersome having to predeclare all the FlipFlops.


No doubt.

I haven't had a chance to try it yet, but do either Avi's or Nathaniel's solutions work in the

"do_something if something..something_else"

form?
 
P

Phil Tomson

No doubt.

I haven't had a chance to try it yet, but do either Avi's or Nathaniel's
solutions work in the

"do_something if something..something_else"

form?

I haven't tried Nathaniel's proposed solution, but Avi's solution takes a
block (where the action occurs), so I don't think it would work that way.
A different method could probably be added to his FlipFLop class that
could work that way.

Phil
 
A

Avi Bryant

Seems to work and be nestable, as in the usage here:
ff = FlipFlop.new
ff2= FlipFlop.new
f = File.open('filename')
f.each_line {|l|
ff.from(l=~/BEGIN/, l=~/END/) do
#inside the BEGIN .. END block
ff2.from(l=~/def/,l=~/end/) do
#inside def .. end block
puts l
end
end
}
But it's more cumbersome having to predeclare all the FlipFlops.

Now that raises an interesting point. Say you had this input:

---
BEGIN
def baz
#oops, syntax error
END

BEGIN
#we shouldn't see this line
END
---

Your code above would break with it (at least, wouldn't produce what I
think most people would expect it to). So would the builtin flipflop
syntax. However, it's easy to solve with a slight modification to the
FlipFlop class:

class FlipFlop
def initialize
@in_range = false
end

def from(start_cond, end_cond)
if start_cond
@in_range = true
@nested = FlipFlop.new
end
yield @nested if @in_range
@in_range = false if end_cond
end
end


ff = FlipFlop.new
f = File.open('test')
f.each_line {|l|
ff.from(l=~/BEGIN/, l=~/END/) do
|nested|
nested.from(l=~/def/,l=~/end/) do
#inside def .. end block
puts l
end
end
}
 
J

John W. Kennedy

Hal said:
I meant it was ugly and obtuse, and no one would ever think otherwise
unless he was used to Perl.

Code with an invisible state always reminds me of COBOL, frankly.

--
John W. Kennedy
"But now is a new thing which is very old--
that the rich make themselves richer and not poorer,
which is the true Gospel, for the poor's sake."
-- Charles Williams. "Judgement at Chelmsford"
 
M

Michael campbell

John said:
Code with an invisible state always reminds me of COBOL, frankly.

Interesting, since everything in COBOL is global and there IS no visibility...
 
R

Robert Klemme

Hal Fulton said:
Phil,

I'm with Nathaniel. Save the functionality but please KILL the
syntax. Only a perler could love it. (No offense meant.)

I haven't thought this through yet. But let's do it with a
method call.

#untested
f.set_context(/BEGIN/,/END/)
f.each_line do |l|
if f.in_context
# do something with l...
else
# whatever
end
end


Comments, Phil? Nathaniel? Others?

I wouldn't put it into File or IO since this is too specialized
functionality. It deserves a class of it's own:

class FlipFlop
def initialize(on, off)
@on, @off = on, off
@state = false
end

def ===(o)
if @state
@state = false if @off === o
else
@state = true if @on === o
end

@state
end
end

io=<<TEXT
foo
BEGIN
bar
BEGIN
baz
END
bar
TEXT

ff = FlipFlop.new /BEGIN/, /END/

io.each_line do |line|
line.chomp!
puts "#{ff === line}: #{line}"
end

Yields

false: foo
true: BEGIN
true: bar
true: BEGIN
true: baz
false: END
false: bar

If you want to include the last line you can do:

class FlipFlop2
def initialize(on, off)
@on, @off = on, off
@state = false
end

def ===(o)
old = @state

if @state
@state = false if @off === o
else
old = @state = true if @on === o
end

old
end
end

Yields:

false: foo
true: BEGIN
true: bar
true: BEGIN
true: baz
true: END
false: bar

Regards

robert
 
G

Gavin Sinclair

I wouldn't put it into File or IO since this is too specialized
functionality. It deserves a class of it's own:
class FlipFlop
def initialize(on, off)
@on, @off = on, off
@state = false
end

def ===(o)
if @state
@state = false if @off === o
else
@state = true if @on === o
end

@state
end
end


Nice implementation. The usage is still more verbose than the FF
operator, though. Perhaps some method to process an enumerable
object?

# Untested
class FlipFlop
def process(enum)
enum.each do |o|
yield(o) if self === o
end
end
end


File.open(path) do |io|
FlipFlop.new(/BEGIN/, /END/).process(io) do |line|
puts line
end
end

Is that any good?

Gavin
 
R

Robert Klemme

Gavin Sinclair said:
Nice implementation.
Thanks!

The usage is still more verbose than the FF
operator, though.

Yeah, but it avoids the speciality of treating ".." and "..." differently
when they appear in an if clause.
Perhaps some method to process an enumerable
object?

# Untested
class FlipFlop
def process(enum)
enum.each do |o|
yield(o) if self === o
end
end
end


File.open(path) do |io|
FlipFlop.new(/BEGIN/, /END/).process(io) do |line|
puts line
end
end

Is that any good?

It looks good. The only thing that bothers me a bit is that the iteration
is kind of "hidden". In Ruby we typically do enum.each{|elem| do sth. with
elem}. It's just a feeling, I can't exactly point to why this might be bad.
The only thing that comes to my mind is that you make the whole block
dependend on the FF's state while you might want to do something else with
the line even if the FF doesn't match. E.g. you could be wanting to do:

io.each_line do |line|
line.chomp!

case line
when ff1
# do stuff
when ff2
# do other stuff
if ff3 === line
# nested
end
end
end

Dunno whether that is realistic.

Regards

robert
 
S

Sabby and Tabby

This one short-circuits, evaluating the left operand when the
flip-flop is false and the right operand when true:

class FlipFlop
attr_accessor :flip

@@FF = Hash.new {|h,k| h[k] = FlipFlop.new}

def FlipFlop.[](id)
@@FF[id]
end

def initialize
@flip = Proc.new if block_given?
@in_range = false
@num = 0
end

def flop(like=:awk)
if @in_range
@in_range = ! yield
else
@in_range = @flip[] or return
@in_range = ! yield if like == :awk
end
@num = String === @num ? 1 : @in_range ? @num + 1 : "#{@num + 1}E0"
end

def flop!(&b)
flop:)sed, &b)
end
end

module Kernel
def flip(id=caller[0])
FlipFlop[id].flip = Proc.new
FlipFlop[id]
end
end

puts ".. and ..."
a = (1..10).map {|i| i==3 .. (i+=0.5 and i==6.5) ? i : -i}
b = (1..10).map {|i| i==3 ... (i+=0.5 and i==6.5) ? i : -i}
p a, b

puts "#flip#flop and #flip#flop!"
a = (1..10).map {|i| flip {i==3}.flop {i+=0.5 and i==6.5} ? i : -i}
b = (1..10).map {|i| flip {i==3}.flop! {i+=0.5 and i==6.5} ? i : -i}
p a, b

Internal state is maintained using #caller info, so an explicit :label
is required if there is more one #flip#flop per line:

a = (1..10).select do |i|
flip:)x) {i==3}.flop {i==7} and flip:)y) {i==5}.flop {i==9}
end

Maybe useful, maybe not, but returns a sequence number, like Perl:

('a'..'z').each {|x|
i = flip {x=~/[ep]/}.flop {x=~/[iw]/}
puts "#{x}: #{i.inspect}"
}

Outputs:

a: nil
b: nil
c: nil
d: nil
e: 1
f: 2
g: 3
h: 4
i: "5E0"
j: nil
k: nil
l: nil
m: nil
n: nil
o: nil
p: 1
q: 2
r: 3
s: 4
t: 5
u: 6
v: 7
w: "8E0"
x: nil
y: nil
z: nil
 
I

Ian Hobson

Michael campbell said:
Interesting, since everything in COBOL is global and there IS no visibility...
PERFORM THROUGH provided more than too much INvisibility. When you
combine that with GO TO that can take you anywhere and an EXIT that only
returns if there was a relevant PERFORM, the opportunities for crap code
were legion.

IIRC every compiler generated self modifying code - it was the easiest
way to handle the semantics of the language.

Ian
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,167
Latest member
SusanaSwan
Top