in reply to Regular Expressions and atomic weights

Your question has been answered well, but since my brain is in parsing mode, I thought I'd bring this up.. The reason it's hard to do this with a regex is that (barring (??{code}) directives in the regex) you can't match arbitrarily deep nested parentheses using regular expressions. Since your data format supports nesting things in parentheses, parsing is a better solution.

Writing a parser for such simple notation is not that hard. What you can do to make it even easier is to combine the weight calculations with actual parsing. This is called syntax-directed evaluation. You don't see syntax-directed evaluation much in the parsing of programming languages, but for simpler expression languages where each part of the expression has a value, and you are parsing the expression for the sole purpose of computing its final value (think of a simple math expression calculator).

use Parse::RecDescent; use List::Util 'sum'; use vars '%weights'; %weights = qw( C 12 O 16 Pb 207 ); my $g = Parse::RecDescent->new(<<'END_GRAMMAR'); weight: compound { $item[1] } compound: group(s) { ::sum( @{$item[1]} ) } group: element /\d+/ { $item[1] * $item[2] } | element { $item[1] } element: /[A-Z][a-z]*/ { $::weights{ $item[1] } } | "(" compound")" { $item[2] } END_GRAMMAR print $g->weight("Pb(CO3)2"), $/; # prints 327
This is probably what those other CPAN modules are doing. Actually, since they do more than just compute the weight, they probably parse the chemical formula into a tree structure first, and do the weight calculation on that tree. If you only do the weights, you can save yourself having to use an awkward intermediate tree representation.

blokhead

Replies are listed 'Best First'.
Re^2: Regular Expressions and atomic weights
by ikegami (Patriarch) on Jul 25, 2005 at 18:25 UTC

    Below is a refactoring that avoids using stuff from main. Using stuff from main prevents you from precompiling your grammar into a module. Also, using stuff from main makes the script incompatible with mod_perl. (I also lined up the productions.)

    use Parse::RecDescent; my $g = Parse::RecDescent->new(<<'END_GRAMMAR'); { use List::Util 'sum'; use vars '%weights'; %weights = qw( C 12 O 16 Pb 207 ); } weight : compound { $item[1] } compound : group(s) { sum( @{$item[1]} ) } group : element /\d+/ { $item[1] * $item[2] } | element { $item[1] } element : /[A-Z][a-z]*/ { $weights{ $item[1] } } | "(" compound ")" { $item[2] } END_GRAMMAR print $g->weight("Pb(CO3)2"), $/; # prints 327

    I was planning on doing P::RD solution for the OP, but I abandonned the idea when others pointed to existing specialized modules. Thanks for filling in the gap.

    Update: The common start of both group productions is very innefficient. Fix:

    weight : compound { $item[1] } compound : group(s) { sum( @{$item[1]} ) } group : element factor { $item[1] * $item[2] } factor : /\d+/ { $item[1] } | { 1 } element : /[A-Z][a-z]*/ { $weights{ $item[1] } } | "(" compound ")" { $item[2] }

      That's hot™. Thanks to you and blokhead both for not ceasing to solve the problem. That's one of the more concise and edifying Parse::RecDescent examples I've seen.