pc88mxer has asked for the wisdom of the Perl Monks concerning the following question:

While answering another SoPW question, I wrote a script to convert the spreadsheet column name IV to an integer:
sub v { ord($_[0]) - ord('A'); } print v('I')*26 + v('V'), "\n";
This yielded the output:
shell$ perl ./iv65536 229 shell$
It then occurred to me that in the column name AB, the A has weight 1 (not weight 0), and so I was off by 26. I therefore modified the formula to:
print (v('I')+1)*26 + v('V'), "\n";
and running it produced:
shell$ perl ./iv65536 9shell$
Why did it print 9, and where did the newline go?

Replies are listed 'Best First'.
Re: Converting "IV" from base 26
by graff (Chancellor) on Jul 01, 2008 at 03:53 UTC
    Looks like you are running afoul of the "special" argument handling that is done by the "print" function, when it tries to figure out whether its first arg is a file handle to print to, or simply the first value to be printed (to the current default output file handle) what ikegami said.

    In any case, I think the better way to fix the original problem is like this:

    sub v{ ord($_[0])-ord("A")+1 }
    That is, your v() sub should return the correct index value for a given letter (between 1 and 26), so that it doesn't have to be fixed by the caller.

    UPDATE: I wondered about your comment regarding "weight 1" vs. "weight 0"... are you sure about that? If we are talking about the column labels in a spreadsheet, "A" is col. 1, "Z" is 26, "AA" is 27, etc. That is, each letter "digit" has a "weight" of 1 (if I understand you terminology correctly).

    In any case, an even better solution is to let the v() sub split the string and handle the power multiplication:

    sub v { my (@letters) = split //, $_[0]; my $sum = 0; my $pwr = 1; while ( @letters ) { my $digit = ord( pop( @letters )) - ord("A") + 1; $sum += $digit * $pwr; $pwr *= 26; } return $sum; } print v("AB"),"\n";
    prints "28", as it should, IMO.
      Your code works, but there's no reason to create (and pop from) the separate @letters array. Additionally, by doing the multiplication first, we can get rid of the $pwr variable, and get to the much simpler (to my mind, at least),
      sub v { my $sum = 0; for (split //, $_[0]) { $sum *= 26; $sum += ord($_) - ord("A") + 1; } return $sum; }
      You could also factor out the ord("A") + 1 part into a separate variable, but I think it's a little easier to read this way (and the cost of the extra calls to ord is probably negligible).

      Looks like you are running afoul of the "special" argument handling that is done by the "print" function,

      That's completely wrong. It has nothing to do with print, and all to do with the meaninglessness of the space before opening paren.

      $ perl -le'sub f { print("@_"); } f (5) + 4' 5

      Or with warnings:

      $ perl -wle'sub f { print("@_"); } f (5) + 4' Useless use of addition (+) in void context at -e line 1. 5

      In the code in question,

      print (v('I')+1)*26 + v('V'), "\n";
      means
      print( v('I')+1 ) * 26 + v('V'), "\n";
Re: Converting "IV" from base 26
by BrowserUk (Patriarch) on Jul 01, 2008 at 03:51 UTC
Re: Converting "IV" from base 26
by poolpi (Hermit) on Jul 01, 2008 at 10:41 UTC

    Another way :

    #!/usr/bin/perl -w use strict; print ' AB in 10 base => ', base10('AB'), "\n"; print ' IV in 10 base => ', base10('IV'), "\n"; sub base10{ my ( %base26, @letters, $sum ); @letters = reverse split '', shift; @base26{ 'A'..'Z' } = 1..26; $sum += $base26{ $letters[$_] } * 26**$_ for 0..$#letters; return $sum; } __END__ Output : AB in 10 base => 28 IV in 10 base => 256

    hth,
    PooLpi

    'Ebry haffa hoe hab im tik a bush'. Jamaican proverb
Re: Converting "IV" from base 26
by ambrus (Abbot) on Feb 09, 2009 at 18:23 UTC