mike nomax
mike nomax

Reputation: 119

Arithmetic Calculation in Perl Substitute Pattern Matching

Using just one Perl substitute regular expression statement (s///), how can we write below:

Every success match contains just a string of Alphabetic characters A..Z. We need to substitute the match string with a substitution that will be the sum of character index (in alphabetical order) of every character in the match string.

Note: For A, character index would be 1, for B, 2 ... and for Z would be 26.

Please see example below:

success match: ABCDMNA  
substitution result: 38  

Note:

1 + 2 + 3 + 4 + 13 + 14 + 1 = 38; 

since

A = 1, B = 2, C = 3, D = 4, M = 13, N = 14 and A = 1.

Upvotes: 4

Views: 3245

Answers (5)

clt60
clt60

Reputation: 63892

Or an short alternative:

echo ABCDMNA | perl -nlE 'm/(.)(?{$s+=-64+ord$1})(?!)/;say$s'

or readable

$s = "ABCDMNA";
$s =~ m/(.)(?{ $sum += ord($1) - ord('A')+1 })(?!)/;
print "$sum\n";

prints

38

Explanation:

  • trying to match any character what must not followed by "empty regex". /.(?!)/
  • Because, an empty regex matches everything, the "not follow by anything", isn't true ever.
  • therefore the regex engine move to the next character, and tries the match again
  • this is repeated until is exhausted the whole string.
  • because we want capture the character, using capture group /(.)(?!)/
  • the (?{...}) runs the perl code, what sums the value of the captured character stored in $1
  • when the regex is exhausted (and fails), the last say $s prints the value of sum

from the perlre

(?{ code })

This zero-width assertion executes any embedded Perl code. It always succeeds, and its return value is set as $^R .

WARNING: Using this feature safely requires that you understand its limitations. Code executed that has side effects may not perform identically from version to version due to the effect of future optimisations in the regex engine. For more information on this, see Embedded Code Execution Frequency.

Upvotes: 1

TLP
TLP

Reputation: 67900

I will post this as an answer, I guess, though the credit for coming up with the idea should go to abiessu for the idea presented in his answer.

perl -ple'1 while s/(\d*)([A-Z])/$1+ord($2)-64/e' 

Since this is clearly homework and/or of academic interest, I will post the explanation in spoiler tags.

- We match an optional number (\d*), followed by a letter ([A-Z]). The number is the running sum, and the letter is what we need to add to the sum.
- By using the /e modifier, we can do the math, which is add the captured number to the ord() value of the captured letter, minus 64. The sum is returned and inserted instead of the number and the letter.
- We use a while loop to rinse and repeat until all letters have been replaced, and all that is left is a number. We use a while loop instead of the /g modifier to reset the match to the start of the string.

Upvotes: 6

abiessu
abiessu

Reputation: 1927

Consider the following matching scenario:

my $text = "ABCDMNA";
my $val = $text ~= s!(\d)*([A-Z])!($1+ord($2)-ord('A')+1)!gr;

(Without having tested it...) This should repeatedly go through the string, replacing one character at a time with its ordinal value added to the current sum which has been placed at the beginning. Once there are no more characters the copy (/r) is placed in $val which should contain the translated value.

Upvotes: 3

choroba
choroba

Reputation: 241758

Can you use the /e modifier in the substitution?

$s = "ABCDMNA";
$s =~ s/(.)/$S += ord($1) - ord "@"; 1 + pos $s == length $s ? $S : ""/ge;
print "$s\n"

Upvotes: 3

Miller
Miller

Reputation: 35198

Just split, translate, and sum:

use strict;
use warnings;

use List::Util qw(sum);

my $string = 'ABCDMNA';

my $sum = sum map {ord($_) - ord('A') + 1} split //, $string;

print $sum, "\n";

Outputs:

38

Upvotes: 3

Related Questions