7
\$\begingroup\$

The task is to sum up scores mentioned in a text file. Scores are floating point numbers as defined by the regexp float immediately preceded by a * character to distinguish them from other numbers in the text. Scientific notation is not permitted.

Example input

Day 1:     
  Task 1: *5 Task 2: *2,8
  Task 3 was the hardest, your score is *-1
… 

The expected sum here is 5 + 2.8 - 1 = 6.8.

my $sum = 0;

my regex float {
                 (<[+-]>?)          # opt. sign
                 (\d+)              # whole part
                 [(<[.,]>)(\d*)]?   # opt. fractional part;
                                    #   comma is a valid separator
               }

for slurp.match( / \* <float> /, :g ) {
  my $f = $_<float>;
  # justification for the next line:
  #  +"5," Cannot convert string to number: trailing characters after number
  #  +"5." Cannot convert string to number: radix point must be followed by one or more valid digits
  $sum += +"$f[1]$f[2].$f[4]0"
}

say $sum;

The code works, but it prints out a large number of warning messages

use of uninitialized value of type Any in string context

I would like to hear suggestions how to write this in a more idiomatic Perl.

In addition, I would like to hear how to make it more an one liner since I guess the code might be written with significantly less lines and still be readable.

\$\endgroup\$
10
\$\begingroup\$

The warning comes from the fact that the third and fourth capture groups are inside an optional part of the regex. Therefore, $f[4] might not be defined.

Rather than concatenating the parts of a decomposed number, I suggest taking all of $f[0] and replacing any comma with a period.

my $sum = 0;

my regex float {
                 <[+-]>?          # opt. sign
                 \d+              # whole part
                 [ <[.,]> \d+ ]?  # opt. fractional part
                                      #comma is vaild separator
               }

for slurp.match( / \* <float> /, :g ) {
    $sum += +$_<float>.subst( /','/ , '.' , :g );
}

say $sum;
\$\endgroup\$
  • \$\begingroup\$ and appending a zero? +"5." Cannot convert string to number: radix point must be followed by one or more valid digits \$\endgroup\$ – user7610 Dec 9 '14 at 18:27
  • \$\begingroup\$ No, don't append 0. Rather, write [(<[.,]>)(\d+)]? in the regex. \$\endgroup\$ – 200_success Dec 9 '14 at 18:29
  • \$\begingroup\$ That is cheating. Scores are floating point numbers as defined by the regexp float immediately preceded by a * character Scores are what they are. "5," is a valid score. You cannot just ignore it 'cause it's not convenient. \$\endgroup\$ – user7610 Dec 9 '14 at 18:32
  • 2
    \$\begingroup\$ 5 is all you care about. Why not discard the trailing comma if there are no digits after it? \$\endgroup\$ – 200_success Dec 9 '14 at 18:33
  • 1
    \$\begingroup\$ I must remember to default to trans instead of subst. Provided it's not pushed too hard, it's typically more maintanable (reads more nicely and is more easily altered to accommodate new cases) than subst. \$\endgroup\$ – raiph May 21 at 13:35
2
\$\begingroup\$

I managed to further compress the answer by @200_success

my regex float { <[+-]>?  \d+  [ <[.,]> \d+ ]? }
say [+] slurp.match( / \* <( <float> )> /, :g )».trans(',' => '.');

It is just two lines and I think it is still quite readable.

\$\endgroup\$
  • \$\begingroup\$ Nice! Is the » intentional, though? \$\endgroup\$ – 200_success Dec 10 '14 at 3:13
  • 1
    \$\begingroup\$ I think that the hyper-dot written as ». looks nicer than the ASCII variant >>. \$\endgroup\$ – user7610 Dec 10 '14 at 10:20
  • 2
    \$\begingroup\$ Your answer was previously deleted probably because it was considered as an alternative solution but not a review of the code. Explaining your reasoning how your solution works and why it is better than the alternatives helps making it a better answer and would help the author of the question and other readers to learn from your thought process. \$\endgroup\$ – Simon Forsberg May 21 at 20:06
1
\$\begingroup\$

I would like to hear how to make it more an one liner since I guess the code might be written with significantly less lines and still be readable.

Excessively compressed (write-only):

say sum m:g/\*<(\-?\d+\,?\d*/».trans: ',' => '.' given slurp

Nice, readable, maintainable code if it can be assumed that later readers will know P6 or have enough patience to read its doc to learn the P6 used:

my $score = / '*' <( '-'? \d+ ','? \d* /;

say sum slurp.match( :g, $score )».trans: ',' => '.'

If one has to assume later readers won't know P6 and won't read the docs without prompting then I might reluctantly add comments:

# See docs.perl6.org/language/regexes#Capture_markers:_%3C(_)%3E 
my $score = / '*' <( '-'? \d+ ','? \d* /;

# See docs.perl6.org/language/operators#methodop_%C2%BB._/_methodop_%3E%3E.
say sum slurp.match( :g, $score )».trans: ',' => '.'
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.