Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Alpha base-26 to base-10...

by eduardo (Curate)
on Jun 30, 2003 at 22:40 UTC ( [id://270352]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings!

So, I have a problem at my current contract that proved to be surprisingly annoying, and my solution looks remarkably inelegant to me. So, I thought to myself: "I'll ask the perlmonks, surely someone will give me a 8 character regexp that solves my problem elegantly!" :)

I have a series of "alphabet based base-26" numbers... what do I mean by this? Well how about some examples so you get the idea...

Base-26Base-10
a1
z26
aa27
az52
ba53
zz702
aaa703

So, basically each position is a power of 26. Pretty reasonable... I wanted a sub that I could easilly pass a string, and receive the base 10 equivalent. The following is my sub and test program...

#!/usr/bin/perl use warnings; use strict; use POSIX qw/ pow /; sub convert_from_base26 { my ($total, $exp); my ($val) = @_; $total += (ord($_) - (ord('a') - 1)) * pow(26, $exp++) for split('', lc(reverse($val))); return $total; } print "Enter a value: "; while (<>) { chomp; print convert_from_base26($_)."\n"; print "Enter a value: "; }

Although the code for the sub is relatively concise, it doesn't look like code I'd like to revisit (not to mention that I need to find a new sub name), as when I look at the code it's not "obvious" what is going on. I think the term I heard at one point that I loved was: "it has too many metasyntatic variables." So, monks... any ideas as to how I could write this to be a more elegant piece of code?

Replies are listed 'Best First'.
Re: Alpha base-26 to base-10...
by pfaut (Priest) on Jun 30, 2003 at 23:02 UTC
    #!/usr/bin/perl -w use strict; sub convert26 { my $v = 0; $v = ($v * 26 + $_ - ord('a') + 1) for unpack("C*",shift); $v; } while (<DATA>) { chomp; print "$_: ",convert26($_),$/; } __DATA__ a z aa az ba zz aaa

    Output:

    a: 1 z: 26 aa: 27 az: 52 ba: 53 zz: 702 aaa: 703
    90% of every Perl application is already written.
    dragonchild
Re: Alpha base-26 to base-10...
by BrowserUk (Patriarch) on Jun 30, 2003 at 23:32 UTC

    Feel free to not use the name:)

    sub b262b10{ my ($n,$x)=(0,'A'); ++$n until $x++ eq $_[0]; ++$n } print $_, ':', b262b10( $_ ), $/ for qw[A Z AA AZ BA ZZ AAA ZBA] A : 1 Z : 26 AA : 27 AZ : 52 BA : 53 ZZ : 702 AAA : 703 ZBA : 17629

    Update: Lest anyone not realise it, this okay as a golf solution, but it is not a serious contender for anything real as it is horribly inefficient.


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller


      Did you say "golf"? :-)

      1 2 123456789012345678901234567 sub b262b10{ my$n;$n++for"a"..lc$_[0];$n }

      27

      Update: Make that 26. I can save a stroke by reusing @_. :-)

      sub b262b10{ $_[1]++for"a"..lc$_[0];pop }
      -sauoq
      "My two cents aren't worth a dime.";
      
        Golf?
        sub b262b10{ ()=a..lc pop }

        Explanation:

        In Golf, there's no need for strict or quotes. A bareword is a string when Perl can't find an identifier with that name. So here, a is just "a", only two strokes shorter.

        pop takes one argument off of @_, and since there's only one argument, it's just the same as shift. lc turns it to lower case.

        .. is the range operator in list context, but the flip-flop operator in scalar context. The construct ()= forces list context. The value of a list assignment is the number of elements of the right member, even though the left value (()) is empty.

        So this sub yields (in scalar context) the number of element between "a" and the string passed as argument. QED

        --bwana147

Re: Alpha base-26 to base-10...
by Limbic~Region (Chancellor) on Jun 30, 2003 at 23:15 UTC
    eduardo,
    The concise, but less readable form:
    #!/usr/bin/perl -w use strict; print base10_it('ba'), "\n"; sub base10_it { my $base_10; my @values = reverse split '' , $_[0]; $base_10 += ((ord($values[$_]) - 96) * 26 ** $_) for (0 .. $#value +s); $base_10; }
    The more readable and verbose form: Cheers - Limbic~Region
Re: Alpha base-26 to base-10...
by sauoq (Abbot) on Jul 01, 2003 at 00:15 UTC

    I'd prefer something more readable than concise. It's a concise enough problem anyway.

    sub b26_to_b10 { my $base_10 = 0; for (split //, lc shift) { $base_10 *= 26; $base_10 += ord() - 96; } return $base_10; }
    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Alpha base-26 to base-10...
by mr_mischief (Monsignor) on Jun 30, 2003 at 23:17 UTC
    Not as short but a bit clearer (to me, anyway) than the others I've seen:
    sub b26_to_b10 { my @digits = reverse split //, shift; my $i = 1; my $result = 0; for ( @digits ) { $result += ( ord(lc($_)) - ord('a') + 1 ) * $i; $i *= 26; } return $result; }


    Christopher E. Stith
Re: Alpha base-26 to base-10...
by wolfger (Deacon) on Jul 01, 2003 at 00:29 UTC
    Wow. I can't believe that nobody else has pointed out the fact that you are *not* using a base 26 system correctly.
    a=0
    z=25
    In base 26, there will be no single-character representation of the number 26. Just as there is no single-character representation of 10 in base 10, or 2 in binary.

    If you want "blank" = 0, a=1, z=26, then you are using base 27.

    Believe nothing, no matter where you read it, or who said it - even if I have said it - unless it agrees with your own reason and your own common sense. -- Buddha
      Actually tilly pointed out that it's not a real base-26 system in the chatterbox. It's the Excel column numbering system to be precise. The problem is that I have no idea what to call it other than a pseudo base-26ish system in which there is no symbol for 0, and the digits are represented by the letters of the english alphabet.
        Well, as you're using it, it's base 27 with no defined symbol for zero. :-)

        Believe nothing, no matter where you read it, or who said it - even if I have said it - unless it agrees with your own reason and your own common sense. -- Buddha
Re: Alpha base-26 to base-10...
by Aristotle (Chancellor) on Jul 01, 2003 at 00:06 UTC
    use strict; use warnings; use List::Util qw(sum); use constant OFFS => 1 - ord 'a'; use constant BASE => ord("z") - ord("a") + 1; sub alphabetic_to_base10 { local $_ = reverse shift; my $fac = 1; sum map +( (OFFS+ord $_)*$fac, $fac*=BASE )[0], /./g; } printf "$_: %d\n", alphabetic_to_base10($_) for qw[a z aa az ba zz aaa + zba]; __END__ a: 1 z: 26 aa: 27 az: 52 ba: 53 zz: 702 aaa: 703 zba: 17629
    Update: OFFS has to be 1 - ord 'a', not - ord 'a', of course.

    Makeshifts last the longest.

Re: Alpha base-26 to base-10...
by jmcnamara (Monsignor) on Jul 01, 2003 at 00:12 UTC

    Here is some similar code that I wrote a while ago as part of Spreadsheet::WriteExcel::Utility. The code is verbatim although I was tempted to take out some of the parentheses:
    # Convert base26 column string to number # All your Base are belong to us. my @chars = split //, $col; my $expn = 0; $col = 0; while (@chars) { my $char = pop(@chars); # LS char first $col += (ord($char) -ord('A') +1) * (26**$expn); $expn++; }

    As an aside. Any particular reason for using POSIX::pow()?

    And as another aside the POSIX strtol() function will convert base 26 (or bases from 2 to 36) to base 10, although not for this base 26 format.

    --
    John.

      John~

      I am surprised that no one else has mentioned this (because some have done it in their code), but this is the sort of situation where you want to avoid using exponentiation (be it pow or **). You should notice that the correct power can be built up by repeatedly multiplying by 26 during each pass through the loop. Making your code into:
      # Convert base26 column string to number # All your Base are belong to us. my @chars = split //, $col; my $expn = 1; $col = 0; while (@chars) { my $char = pop(@chars); # LS char first $col += (ord($char) -ord('A') +1) * $expn; $expn *= 26; }

      While a really good optimizer *might* do this for you, I doubt that many do, and I am relatively certain that Perl won't since it is so heavily introspective. This is the sort of optimization I always keep my eyes out for cause it can save a lot of work.

      Boots
      ---
      Computer science is merely the post-Turing decline of formal systems theory.
      --???

        this is the sort of situation where you want to avoid using exponentiation (be it pow or **.

        I guess you mean that it should be avoided because exponentiation is an expensive operation.

        In the code above there would only be at most two iterations through the loop so the effect is small. Nevertheless, it is still easy to avoid it as you have shown.

        I wrote that code more than two years ago. If I had to write it again I would probably approach it in the same way. But I would like to have come up with something like sauoq's solution which I think is the nicest solution in this thread (apart form bwana147 golf). ++ in both cases.

        --
        John.

      Greetings!

      I was using POSIX::pow() because, and am about to hide my head in great shame... I didn't know that perl had the ** operator. I know, I'm a tool.

      Secondly, this is actually to get things out of an excell spreadsheet-ish sort of thing, unfortunately i've discovered that the wonderful human being who created the spreadsheet did things like erase columns, so AE goes right to AG... which makes slicing on numeric offsets *really* a pain in the *******. However, I'm definetly going to check out your module...

Re: Alpha base-26 to base-10...
by Anonymous Monk on Jul 01, 2003 at 02:47 UTC
    How about:
    use Math::BaseCalc;
    ..And then...
    my $base_26 = Math::BaseCalc->new(digits=>['A'..'Z']); print "Enter a value: "; while (<>) { chomp; print $base_26->from_base($_)."\n"; print "Enter a value: "; }
    CPAN is a beautiful thing. FWIW, there is a bug in your base numbers. In a base-26 set "A" == 0, "B" == 1, etc. TTYL
Re: Alpha base-26 to base-10...
by Aristotle (Chancellor) on Jul 01, 2003 at 09:58 UTC
    Another take.
    use strict; use warnings; use constant OFFS => 1 - ord 'a'; use constant BASE => ord("z") - ord("a") + 1; sub alphabetic_to_base10 { local $_ = shift; my $num = 0; $num = ($num * BASE) + (OFFS + ord $_) for /./g; return $num; } printf "$_: %d\n", alphabetic_to_base10($_) for qw[a z aa az ba zz aaa + zba]; __END__ a: 1 z: 26 aa: 27 az: 52 ba: 53 zz: 702 aaa: 703 zba: 17629

    Makeshifts last the longest.

Re: Alpha base-26 to base-10...
by Util (Priest) on Jul 02, 2003 at 01:35 UTC

    Yet another take. Clear and elegant, but quite inefficient given large maximums.

    { my $count; my %h = map {$_ => ++$count} 'a'..'zzz'; sub alphabetic_to_base10 { return $h{ lc( $_[0] ) } or die; } }

      That's simply a pre-memoized version of BrowserUk's approach.

      Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://270352]
Approved by Thelonius
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (9)
As of 2024-03-28 09:29 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found