Perl to Ruby: regex captures to assignment

Hello all,

I am converting a Perl program to Ruby in order to learn Ruby. There is
an expression that takes a string, (“1 3/4” or “5”, for example)
determines what kind of fraction was entered, and assigns variables (w,
n, d), appropriately. If a whole number was entered, then it assigns
the variables differently, of course.

The expression first checks for the presence of a slash ("/") to
determine if it is a fraction or whole number. Then, using the
conditional operator, assigns the variables, appropriately.

Here are the two expressions:

Perl:

my ($w, $n, $d) = ( $frac_str =~ /\// )
        ? $frac_str =~ /(?:(\S+) )??(\S+)\/(\S+)/
        : ( 1, $frac_str, 0 );

Ruby:

w, n, d = frac_str.match(/\//) \
    ? frac_str.match(/(?:(\S+) )*?(\S+)\/(\S+)/).captures \
    : 0, frac_str, 1
puts "w: #{w}, n: #{n}, d: #{d}"  # check assignment

I used irb to test the regex and it works just fine:

exp = Regexp.new(/(?:(\S+) )?(\S+)/(\S+)/)
=> /(?:(\S+) )
?(\S+)/(\S+)/

str = “1 3/4”
=> “1 3/4”

n, w, d = str.match(exp).captures
=> [“1”, “3”, “4”]

w
=> “3”

n
=> “1”

d
=> “4”

But, the above version outputs:

w: 134, n: 1 3/4, d: 1

It appears to recurse over the regex and duplicate the captures. But,
since I am new to this language, I know I can use an ‘if…else’
statement, but since I am coming from Perl, I figured that a similar
expression would “just work”! :slight_smile:

Thanks for your time and input,

Derrick

As it is, the parser is interpreting the ternary operation as the first
item in the list, like:

tmp = frac_str.match(/\//) \
  ? frac_str.match(/(?:(\S+) )*?(\S+)\/(\S+)/).captures \
  : 0
w, n, d = tmp, frac_str, d

Subsequently puts(tmp) is calling Array#to_s which concatenates the
elements, so you see “134”

I got it to work by turning the third parameter of the ternary operation
into an explicit array:

w, n, d = frac_str.match(/\//) \
  ? frac_str.match(/(?:(\S+) )*?(\S+)\/(\S+)/).captures \
  : [0, frac_str, 1]  # <= !!!
puts "w: #{w}, n: #{n}, d: #{d}"  # check assignment

Outputs: w: 1, n: 3, d: 4


Matthew K., B.Sc (CompSci) (Hons)
http://matthew.kerwin.net.au/
ABN: 59-013-727-651

“You’ll never find a programming language that frees
you from the burden of clarifying your ideas.” - xkcd

On 20 December 2012 09:07, Matthew K. [email protected] wrote:

Sorry for replying to myself. I just noticed that the Perl code also
had
an explicit array,

 : ( 1, $frac_str, 0 )

Those brackety thingies are important, no matter which language you’re
using. :wink:

Try this:

w, n, d = frac_str =~ %r{/}
? frac_str.match(%r{(?:(\S+) )*?(\S+)/(\S+)}).captures
: [1, frac_str, 0]

I’ve use %r{} to quote regex, so no need to escape for /

On Thu, Dec 20, 2012 at 8:45 AM, Derrick B. [email protected]
wrote:

conditional operator, assigns the variables, appropriately.

=> “1”
statement, but since I am coming from Perl, I figured that a similar
expression would “just work”! :slight_smile:

Thanks for your time and input,

Derrick


Posted via http://www.ruby-forum.com/.


Regards,
Wei Feng

03 9005 3441 | M 0413 658 250 | [email protected] | http://wei.feng.id.au

frac_str = “1 3/4”

w, n, d = frac_str.match(///)
? frac_str.match(/(?:(\S+) )*?(\S+)/(\S+)/).captures
: [0, frac_str, 1]

puts “w: #{w}, n: #{n}, d: #{d}” # check assignment

–output:–
w: 1, n: 3, d: 4

frac_str = “1 3/4”

md = frac_str.match %r{(\d+ )?(\d+)/(\d+)}
w, n, d = md ? md.captures : [frac_str.to_i, 0, 1]

puts “w: #{w}, n: #{n}, d: #{d}” # check assignment

–output:–
w: 1 , n: 3, d: 4

Matthew K. wrote in post #1089658:

On 20 December 2012 09:07, Matthew K. [email protected] wrote:

Sorry for replying to myself. I just noticed that the Perl code also
had
an explicit array,

 : ( 1, $frac_str, 0 )

That isn’t an array in perl–it’s a list. Ruby has a similar concept,
but as the op found out: the direct translation doesn’t work in this
case.

7stud – wrote in post #1089660:

Matthew K. wrote in post #1089658:

On 20 December 2012 09:07, Matthew K. [email protected] wrote:

Sorry for replying to myself. I just noticed that the Perl code also
had
an explicit array,

 : ( 1, $frac_str, 0 )

That isn’t an array in perl–it’s a list. The direct translation from
perl to ruby doesn’t work in this case.

Semantically, and in the context, a Perl list is analogous with a ruby
array. Since the languages are syntactically different (i.e. ruby
doesn’t allow delimited lists within a statement) and transliteration
is, as you say, impossible, the most direct translation from a Perl list
is to a ruby array.

If I, as a ruby programmer, when talking to a Perl programmer, say
“array” when clearly the syntax includes round parentheses, I’m fairly
confident they should be able to understand my intention.

If my slip was really that offensive, let me retract and rephrase as:

Sorry for replying to myself. I just noticed that the Perl code also
had an explicit /list/,

Irrespective, it is still true that “those brackety thingies are
important, no matter which language you’re using.”

how about this? use the magic of =~ to assign the local variables

w, n, d = 0, frac_str, 1 unless /(?:(?\S+) )*?(?\S+)/(?\S+)/
=~ frac_str

First of all, thanks for the fast responses!

Secondly, I swear I tried bracketing the false side of the conditional
(pg 98, section 4.5.5.3 - “The Ruby P.ming Language” book), but I
remember it assigning the array to the first variable - w.

In Perl, there are times when “those brackety thingies” are not
necessary, but as a programmer, I like readabiliy (yes, still talking
about Perl :slight_smile: ) and will include them when it just makes sense.

Thanks all!

On Thu, Dec 20, 2012 at 1:38 AM, Derrick B. [email protected]
wrote:

First of all, thanks for the fast responses!

Secondly, I swear I tried bracketing the false side of the conditional
(pg 98, section 4.5.5.3 - “The Ruby P.ming Language” book), but I
remember it assigning the array to the first variable - w.

I would choose a completely different approach: I would have a single
expression for matching and decide which assignments to make based on
the value of one of the capturing groups in the conditional branch:

[“1 3/4”, “5”].each do |s|
puts s

if %r{\A(\d+)(?:\s+(\d+)/(\d+))?\z} =~ s
w, n, d = $2 ? [$1.to_i, $2.to_i, $3.to_i] : [1, $1.to_i, 0]
printf “%4d %4d %4d\n”, w, n, d
else
$stderr.puts “No match #{s}”
end
end

I also spiced the regexp a bit more to be more restrictive.

In Perl, there are times when “those brackety thingies” are not
necessary, but as a programmer, I like readabiliy (yes, still talking
about Perl :slight_smile: ) and will include them when it just makes sense.

If you like readability then why are you using Perl in the first place?
:slight_smile:

Cheers

robert

Robert K. wrote in post #1089733:

I would choose a completely different approach: I would have a single
expression for matching and decide which assignments to make based on
the value of one of the capturing groups in the conditional branch:

[“1 3/4”, “5”].each do |s|
puts s

if %r{\A(\d+)(?:\s+(\d+)/(\d+))?\z} =~ s
w, n, d = $2 ? [$1.to_i, $2.to_i, $3.to_i] : [1, $1.to_i, 0]
printf “%4d %4d %4d\n”, w, n, d
else
$stderr.puts “No match #{s}”
end
end

I also spiced the regexp a bit more to be more restrictive.

I am prompting for input, so I would be parsing individual strings and
not a list of them. Though I will remember that for future static
testing. Also, I and using the “\S+” to handle negative rational
numbers: “-1 3/4”.

If you like readability then why are you using Perl in the first place?
:slight_smile:

Because Perl is awesome! That is why I chose Ruby as my next personal
choice of languages to learn. I just completed a quarter of Programming
Language Concepts where I was introduced to Lisp, but was also required
to use some Python. I know Python is a fascinatng language, but I am
not yet a big fan of it. The final assignment was write a class to work
with fractions. It started as a C++ class, then migrated to Perl (for
fun), and now Ruby (more fun).

I attached the two versions, Ruby and Perl. The Perl version uses a
“class”, also. Feel free to suggest how to make the Ruby version more
Ruby-ish! My goal is to take more advantage of the OO aspects of Ruby,
and not just have it look like Perl.

Thanks,

Derrick

7stud – wrote in post #1089755:

%r{
(
[-]?
\d+
)?
\s*
(\d+)
/
(\d+)
}xms
)

That works, but it needs another “[-]?” in the second group to handle
negative numerators, or whole numbers:

%r#(?:([-]?\d+) )??([-]?\d+)/(\d+)#xms

…but I should probably add the “\s*” and not expect a single space. I
got the “??” from Perl, but it seems to not be a problem in Ruby.

Lastly, my code I posted did not convert terms to improper fractions,
correctly. Here is the code that works:

Ruby:
n = ( w == w.abs ) ?
n + w * d :
-1 * ( n - w * d ) if d != 0 # to negative improper fraction

Perl:
$n = ( $w == abs($w) )
? $n + $w * $d # to positive improper fraction
: -1 * ( $n - $w * $d ) if $d; # to negative improper fraction

…which leads me to another question:

Why is it that when I try to add
a comment after a line continuation backslash, Ruby says:

./ratnum.rb:14: syntax error, unexpected $undefined
n = ( w == w.abs ) ? \ #
^
./ratnum.rb:15: syntax error, unexpected ‘:’, expecting keyword_end

Ruby should respect my need for comments! :slight_smile:

Derrick B. wrote in post #1089748:

I am prompting for input, so I would be parsing individual strings and
not a list of them. Though I will remember that for future static
testing. Also, I and using the “\S+” to handle negative rational
numbers: “-1 3/4”.

test_data = [
“3/4”,
“1 3/4”,
“-1 3/4”,
‘hello’
]

test_data.each do |str|
match_data = str.match(
%r{
(
[-]?
\d+
)?
\s*
(\d+)
/
(\d+)
}xms
)

w, n, d = match_data ? match_data.captures: [str, 0, 1]
puts “%s %s %s” % [w, n, d]

end

–output:–
3 4
1 3 4
-1 3 4
hello 0 1

On Fri, Dec 21, 2012 at 12:59 PM, Derrick B. <[email protected]
wrote:

…Lastly, my code I posted did not convert terms to improper
fractions,…

not to answer your questions, but, i’d probably go a different route

eg,

require ‘rational’
[“1 3/4”, “-2 1/2”, “5/8”, “-1/3”, “5”, “-5”].map do |mix|
mix.split.map{|x| Rational(x)}
end

=> [ [(1/1), (3/4)], [(-2/1), (1/2)], [(5/8)], [(-1/3)], [(5/1)],
[(-5/1)] ]

kind regards -botp

botp wrote in post #1089780:

On Fri, Dec 21, 2012 at 12:59 PM, Derrick B. <[email protected]
wrote:

…Lastly, my code I posted did not convert terms to improper
fractions,…

not to answer your questions, but, i’d probably go a different route

eg,

require ‘rational’
[“1 3/4”, “-2 1/2”, “5/8”, “-1/3”, “5”, “-5”].map do |mix|
mix.split.map{|x| Rational(x)}
end

=> [ [(1/1), (3/4)], [(-2/1), (1/2)], [(5/8)], [(-1/3)], [(5/1)],
[(-5/1)] ]

kind regards -botp

Interesting! Coming from Perl, I figured there would be a gem to handle
fractions.

edit: this program was derived from an assignment to create a C++ class
to handle rational numbers, so it went from a C++ class, to a Perl
package, to a Ruby class. So, everything “by hand” :slight_smile: Personally, I
like coding the underlying methods for practice, I do need to learn more
CPAN and gem capabilities.

Thanks!

Derrick B. wrote in post #1089779:

Why is it that when I try to add
a comment after a line continuation backslash, Ruby says:

./ratnum.rb:14: syntax error, unexpected $undefined
n = ( w == w.abs ) ? \ #
^
./ratnum.rb:15: syntax error, unexpected ‘:’, expecting keyword_end

Ruby should respect my need for comments! :slight_smile:

See if you can make sense of this line:

result = true ? \ n #your comment “true value” : “false value”

which is equivalent to:

result = true ? \ n

That works, but it needs another “[-]?” in the second group to handle
negative numerators, or whole numbers:

Right, sorry. I would try to capture it out front so that I wouldn’t
have to
repeat myself. By the way, if you haven’t played with Rubular, you
should
give it a try:

I got the “??” from Perl, but it seems to not be a problem in Ruby.

I don’t see how it is pertinent to your regex.

On Thu, Dec 20, 2012 at 7:23 PM, Derrick B. [email protected]
wrote:

w, n, d = $2 ? [$1.to_i, $2.to_i, $3.to_i] : [1, $1.to_i, 0]
printf "%4d %4d %4d\n", w, n, d

else
$stderr.puts “No match #{s}”
end
end

I also spiced the regexp a bit more to be more restrictive.

I am prompting for input, so I would be parsing individual strings and
not a list of them.

That was just for demonstration purposes.

Though I will remember that for future static
testing. Also, I and using the “\S+” to handle negative rational
numbers: “-1 3/4”.

There’s still a better way to do that then just match everything non
whitespace. How would you parse “:s9d2++*3h43” as a number?

[“1 3/4”, “5”, ‘-23’, ‘-23 -4/4’].each do |s|
puts s

if %r{\A([-+]?\d+)(?:\s+([-+]?\d+)/(\d+))?\z} =~ s
w, n, d = $2 ? [$1.to_i, $2.to_i, $3.to_i] : [1, $1.to_i, 0]
printf “%4d %4d %4d\n”, w, n, d
else
$stderr.puts “No match #{s}”
end
end

Btw, did I mention that I find your variable assignment weird? I’d
rather do

[“1 3/4”, “5”, ‘-23’, ‘-23 -4/4’].each do |s|
puts s

if %r{\A([-+]?\d+)(?:\s+([-+]?\d+)/(\d+))?\z} =~ s
w, n, d = $2 ? [$1.to_i, $2.to_i, $3.to_i] : [$1.to_i, 0, 1]
printf “%4d %4d %4d\n”, w, n, d
else
$stderr.puts “No match #{s}”
end
end

If you like readability then why are you using Perl in the first place?
:slight_smile:

Because Perl is awesome!

Hm… It’s been a while that I thought that (but I did actually
once). IMHO Perl has gone over the edge of usefulness long ago.

That is why I chose Ruby as my next personal
choice of languages to learn.

Good choice!

I just completed a quarter of Programming
Language Concepts where I was introduced to Lisp, but was also required
to use some Python. I know Python is a fascinatng language, but I am
not yet a big fan of it. The final assignment was write a class to work
with fractions. It started as a C++ class, then migrated to Perl (for
fun), and now Ruby (more fun).

:slight_smile:

I attached the two versions, Ruby and Perl. The Perl version uses a
“class”, also. Feel free to suggest how to make the Ruby version more
Ruby-ish! My goal is to take more advantage of the OO aspects of Ruby,
and not just have it look like Perl.

I don’t have the time right now but you should definitively use class
Rational. Also your parsing of the command line could be better:

do
print "Fraction 1: "
input = gets or break
f1 = parse_fraction(input)

print "Fraction 2: "
input = gets or break
f2 = parse_fraction(input)

print "Command: "
input = gets or break

case input
when “A”
puts f1 + f2
when “S”
puts f1 - f2

else
abort “Invalid command: #{input}”
end

end until cmd == “Q”

puts “Quitting”

You should also not implement #to_string but rather #to_s and then use
string interpolation for constructing your outputs.

Kind regards

robert

7stud – wrote in post #1089800:

See if you can make sense of this line:

result = true ? \ n #your comment “true value” : “false value”

which is equivalent to:

result = true ? \ n

I can see that, but it is still odd.