17
\$\begingroup\$

Your task is when given a chemical formula as input, output the total number of atoms in the formula.

Input

A chemical formula in any reasonable format. Each chemical formula has the following:

  • A leading coefficient, which is a number \$N > 1\$.
  • At least 0 groupings of elements surrounded by parentheses.
    • Groupings can have both elements and groupings inside of them.
  • At least 1 element.
    • Elements match the regex [A-Z][a-z]*.
  • Each grouping and element can have an optional subscript, which is an _ followed by a number \$N > 1\$

Output

The total number of atoms in the formula. Rules for counting are as follows:

  • A coefficient before the formula multiplies the total count that many times.
  • A subscript multiples the group or atom it follows that many times.
  • An element with no coefficient or subscript represents 1 atom.

Testcases

In -> Out (Notes)
H_2O -> 3
CO_2 -> 3
Co_2 -> 2 (2 cobalts)
3 C_2H -> 9
EeeEeuEetUue -> 4
3 (C_21H_30O_2)_3 -> 477 (= 3 * (53) * 3)
32H -> 32
C_10H_16N_5O_13P_3 -> 47
(Ga(U(Y_2)_3)_4)_5 -> 145 (= (1 + (1 + (2 * 3)) * 4) * 5)
Ti(Ca_2(HSO_4)_2)_2 -> 29 (= 1 + (2 + (1 + 1 + 4) * 2) * 2))

This is , so shortest code wins!

\$\endgroup\$
7
  • \$\begingroup\$ Related that asks for number of each atom \$\endgroup\$
    – bigyihsuan
    Commented Dec 21, 2020 at 22:02
  • \$\begingroup\$ @Arnauld yep, miscalculation \$\endgroup\$
    – bigyihsuan
    Commented Dec 21, 2020 at 22:21
  • \$\begingroup\$ @Arnauld another miscalculation \$\endgroup\$
    – bigyihsuan
    Commented Dec 21, 2020 at 23:10
  • 1
    \$\begingroup\$ Maybe you should define the chemical formula with more detail: What syntax are aimed to support. Also, is it possible to represent \$ {\rm {KAl(SO_{4})_{2}\cdot 12H_{2}O}} \$ with current syntax? \$\endgroup\$
    – tsh
    Commented Dec 22, 2020 at 5:34
  • \$\begingroup\$ @tsh I actually had an entire EBNF while this was in the sandbox and someone pointed out it'd turn into an input validation challenge instead. Besides, what you have there isn't a valid input regardless (coefficient in the middle of the formula) \$\endgroup\$
    – bigyihsuan
    Commented Dec 22, 2020 at 17:47

8 Answers 8

10
\$\begingroup\$

JavaScript (ES6), 87 bytes

s=>eval(s.replace(/[A-Z][a-z]*|_|^\d+|\(/g,s=>+s?`n=${s};n*=`:s>{}?"*":"+"+(s>"0"||s)))

Try it online!

How?

Matched patterns and replacements are summarized in the following table.

 pattern     | replacement
-------------+-------------------------------------------
 [A-Z][a-z]* | "+true"
 _           | "*"
 ^\d+        | "n=N;n*=" (where N is the matched number)
 \(          | "+("

For instance, "3 (C_21H_30O_2)_3" is turned into:

"n=3;n*= +(+true*21+true*30+true*2)*3"

This string is simply evaluated as JS code.

\$\endgroup\$
2
  • \$\begingroup\$ What it the reason for using "true"? Wouldn't "1" work as well? \$\endgroup\$
    – lisyarus
    Commented Dec 22, 2020 at 8:37
  • 2
    \$\begingroup\$ @lisyarus He doesn't use a literal true, it's just that the expression s>"0"||s results in either true or (. \$\endgroup\$
    – Neil
    Commented Dec 22, 2020 at 10:24
2
\$\begingroup\$

Retina 0.8.2, 81 79 bytes

\d+
$*
(1+) (.+)
($2)_$1
[A-Z][a-z]*(_|())
$#2$*
+1`(\((1+)\)_)1|\(1+\)_
$2$1
1

Try it online! Link includes test cases. Explanation:

\d+
$*

Convert the numbers to unary.

(1+) (.+)
($2)_$1

Turn any coefficient into a group.

[A-Z][a-z]*(_|())
$#2$*

Delete all elements with suffixes, but if an element did not have a suffix, then replace it with 1.

+1`(\((1+)\)_)1|\(1+\)_
$2$1

Multiply out all of the groupings. (These have to be done one at a time to be able to collect the results correctly.)

1

Convert to decimal.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Ti(Ca_2(3 HSO_4)_2)_2 is not a valid input \$\endgroup\$
    – bigyihsuan
    Commented Dec 21, 2020 at 23:11
  • \$\begingroup\$ @bigyihsuan I'm glad we don't have to support it, btw. Thanks for clarifying. \$\endgroup\$
    – Arnauld
    Commented Dec 22, 2020 at 0:41
2
\$\begingroup\$

Perl 5 -p, 57 bytes

y/_a-z/*/d;s/^\d+\K.*/*($&)/;s/\pL/+1/g;s/\(/+(/g;$_=eval

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Ruby, 88 bytes

A port of Arnauld's answer in Ruby!

p eval gsub(/_|\(/,?_=>?*,?(=>'+(').gsub(/[A-Z][a-z]*/,'+1').gsub /^\d+/,"k=#{'\&'};k*="

Try it online!

\$\endgroup\$
1
\$\begingroup\$

PHP, 111 bytes

eval('$n='.preg_replace(["/^(\d+)/","/[A-Z][a-z]*/","/_/","/\(/"],['$1;$n*=',"+1","*","+("],$argn).';');echo$n;

Try it online!

Just a port of Arnauld's clever trick with eval and some adaptations:

  • in PHP it was shorter to use a list of regexes instead of using preg_replace_callback (what a long function name)
  • the evaled string has to be valid php prefixed with $n= and ended by ; (in PHP eval does not return the result), example with "Ti(Ca_2(HSO_4)_2)_2": $n=+1+(+1*2+(+1+1+1*4)*2)*2;
  • output of $n has to be explicit

Longer than what I thought, but "c'est de bonne guerre ma bonne dame", it's our good old PHP after all

\$\endgroup\$
1
\$\begingroup\$

PowerShell, 92 bytes

I think there's definitely some room for optimization here; A lot of the bytes are just there to handle the cases with the leading "\d "

$t=1;-split$args|%{$t*=$_-replace'\(','+('-creplace'[A-Z][a-z]*','+1'-replace'_','*'|iex};$t

Try it online!

Honorary Mention, PowerShell, 99 92 bytes

This was my first solution, and I really liked it. Including it here because I think it's fun.

$o="'-creplace'";"'$args$o^(\d)(.*)','`$1*(`$2)$o\(','+($o[A-Z][a-z]*','+1$o`_','*'"|iex|iex

Try it online!

\$\endgroup\$
4
  • \$\begingroup\$ 0. It's impressed! Thanks! 1. A leading coefficient may appear only once, so you can use $args-replace' (.+)','*($1)' instead $t=1;-split$args|%{$t*=. \$\endgroup\$
    – mazzy
    Commented Dec 22, 2020 at 22:40
  • \$\begingroup\$ 2. Your first solution is the best! With a little golf Try it online!. Thanks again! \$\endgroup\$
    – mazzy
    Commented Dec 22, 2020 at 22:40
  • \$\begingroup\$ A regexp is greedy. so 1 byte less :) \$\endgroup\$
    – mazzy
    Commented Dec 22, 2020 at 22:50
  • \$\begingroup\$ Impressive saves, as always, @mazzy!! :D I knew there was something to the honorary mention solution, I just couldn't crack it on my own! At the point of saving me that many bytes, you may as well just consider the solution your own! :) \$\endgroup\$ Commented Dec 22, 2020 at 23:34
1
\$\begingroup\$

PowerShell, 84 83 88 bytes

inspired by Honorary Mentioned Solution by Zaelin Goodman

-1 byte thanks to Zaelin Goodman

+5 bytes the 32H test case added

$o="'-creplace'"
"'$args$o^\d+','`$0*($'')#$o\(','+($o[A-Z][a-z]*','+1$o`_','*'"|iex|iex

Try it online!

Powershell expands the formula 32H to:

'32H'-creplace'^\d+','$0*($'')#'-creplace'\(','+('-creplace'[A-Z][a-z]*','+1'-creplace'_','*'

The first iex calculates this expression to:

32*+(+1)#+1

The second iex calculates the expression before the comment to 32.


Another example. Powershell expands the formula 3 (C_21H_30O_2)_3 to:

'3 (C_21H_30O_2)_3'-creplace'^\d+','$0*($'')#'-creplace'\(','+('-creplace'[A-Z][a-z]*','+1'-creplace'_','*'

The first iex calculates this expression to:

3*+( +(+1*21+1*30+1*2)*3)# +(+1*21+1*30+1*2)*3

The second iex calculates the expression before the comment to 477.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Brilliant solution; -1 byte by changing \S+ to .+ (since there should never be more whitespace) Try it online! \$\endgroup\$ Commented Dec 23, 2020 at 13:06
1
\$\begingroup\$

05AB1E, 39 37 bytes

AмAuS„+1:'_'*:'(„+(.:.γd}ćDdi'*«}šJ.E

Similar approach as the other answers, except without regex. Especially the ^\d+ to N* therefore costs quite a few bytes (16 to be exact: .γd}ćDdi'*«}šJ).

Try it online or verify all test cases.

Explanation:

Aм           # Remove all lowercase letters from the (implicit) input-string
AuS„+1:      # Replace all uppercase letters with "+1"
'_'*:        # Replace all "_" with "*"
'(„+(.:     '# Replace all "(" with "+("
.γ }         # Adjacent group the string by:
  d          #  Whether it's a digit
    ć        # Extract head; pop and push the remainder-list and first item
             # separated to the stack
     D       # Duplicate this first item
      di     # Pop, and if it's a (non-negative) integer:
        '*« '#  Append "*" to it
       }     # After the if-statement:
        š    # Prepend the potentially modified first item back to the list
         J   # Join everything together again
.E           # And evaluate and execute it as Python code
             # (after which the result is output implicitly)

Verify all test cases without the .E.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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