Peter’s blog ✴ Week 387 ✴ 17 August 2026

THE WEEKLY CHALLENGE
Ones and atoms

The Perl Camel

Task 2

Atoms count

You are given a chemical formula with elements, numbers, and parentheses. Write a script to count the total number of each type of atom by expanding all grouped multipliers. Then, format and return the final inventory as a single string sorted alphabetically by element name, including the total count only if it is greater than 1.

Examples


Example 1
Input: $formula = '((N2O)3(H2O)2)2'
Output: 'H8N12O10'
Step 1: Expand the innermost parentheses
    (N2O)3 => N = 2*3 = 6, O = 1*3 = 3 => N6O3
    (H2O)2 => H = 2*2 = 4, O = 1*2 = 2 => H4O2
Step 2: Combine inside the outer parentheses
    Formula becomes: (N6O3 H4O2)2
    Sum up identical elements inside: (N6 H4 O5)2
Step 3: Apply the outer multiplier
    N = 6*2 = 12
    H = 4*2 = 8
    O = 5*2 = 10
Step 4: Sort alphabetically and format
    Alphabetical order: H, N, O
    Counts: H: 8, N: 12, O: 10

Example 2
Input: $formula = 'Mg3(PO4)2'
Output: 'Mg3O8P2'
Step 1: Parse ungrouped elements
    Mg3 => Mg = 3
Step 2: Expand parentheses (PO4)2
    P = 1*2 = 2
    O = 4*2 = 8
Step 3: Total up counts
    Mg = 3
    P  = 2
    O  = 8
Step 4: Sort alphabetically and format
    Alphabetical order: Mg, O, P
    Counts: Mg: 3, O: 8, P: 2

Example 3
Input: $formula = '(((H)2)3)4'
Output: 'H24'
Step 1: Expand innermost level (H)2
    H = 1*2 = 2 => formula becomes ((H2)3)4
Step 2: Expand middle level (H2)3
    H = 2*3 = 6 => formula becomes (H6)4
Step 3: Expand outer level (H6)4
    H = 6*4 = 24
Step 4: Sort alphabetically and format
    Single element: H: 24

Example 4
Input: $formula = 'NaCl3(O2(S10)2)2Mg'
Output: 'Cl3MgNaO4S40'
Step 1: Expand innermost parentheses (S10)2
    S = 10*2 = 20 => inner formula becomes => O2S20
Step 2: Expand outer parentheses (O2S20)2
    O = 2*2  = 4
    S = 20*2 = 40
Step 3: Combine all parts
    Ungrouped start: Na (Na = 1), Cl3 (Cl = 3)
    Expanded middle: O = 4, S = 40
    Ungrouped end: Mg (Mg = 1)
Step 4: Sort alphabetically and format
    Alphabetical order: Cl (3), Mg (1), Na (1), O (4), S (40)
    Omit the number 1 for Mg and Na.

Example 5
Input: $formula = 'Z2Y3(X2W)2'
Output: 'W2X4Y3Z2'
Step 1: Parse ungrouped elements
    Z2 => Z = 2
    Y3 => Y = 3
Step 2: Expand parentheses (X2W)2
    X = 2*2 = 4
    W = 1*2 = 2
Step 3: Total up counts
    W = 2, X = 4, Y = 3, Z = 2
Step 4: Sort alphabetically and format
    Alphabetical order: W (2), X (4), Y (3), Z (2)

Analysis

Some of these chemical formulae are rather strange, so pleased don't try synthesising them at home.

I approached this task by splitting the supplied string working backwards, ie right to left. A token comprises:

  • a closing bracket followed by digits ($n)
  • just digits ($m)
  • an element symbol, eg H or He ($x)
  • an opening bracket
  • a closing bracket

I work right to left so that when I encounter an element symbol, I have already parsed the number following it, and any numbers following the brackets which enclose the symbol.

I then increment $c{$x} by $n * $m so that when I'm finished, for example, $c{'He'} == 3.

And then sort keys %c gives the answer.

Try it 

Your input:



eg: Mg3(PO4)2

Script


#!/usr/bin/perl

# Blog: http://ccgi.campbellsmiths.force9.co.uk/challenge/387/2

use v5.26;    # The Weekly Challenge - 2026-08-17
use utf8;     # Week 387 - task 2 - Atoms count
use warnings; # Peter Campbell Smith
binmode STDOUT, ':utf8';
use Encode;

atoms_count('((N2O)3(H2O)2)2');
atoms_count('Mg3(PO4)2');
atoms_count('(((H)2)3)4');
atoms_count('NaCl3(O2(S10)2)2Mg');
atoms_count('Z2Y3(X2W)2');

sub atoms_count {
    
    my ($f, $m, $n, %c, @s, $k);
    
    # initialise
    $f = shift;
    say qq[\nInput:  '$f'];
    $m = $n = 1;
    %c = ();

    # tokenise
    $f = reverse($f);
    while ($f) {
        # digits + close bracket
        if ($f =~ m|^(\d+)\)(.*)|) {
            push @s, $1;
            $m *= $1;            
        
        # digits
        } elsif ($f =~ m|^(\d+)(.*)|) {
            $n = reverse($1);

        # symbol A or Ab            
        } elsif ($f =~ m|^([a-z]?[A-Z])(.*)|) {
            $c{reverse($1)} += $n * $m;
            $n = 1;
        
        # open bracket
        } elsif ($f =~ m|^(\()(.*)|) {
            $m /= pop @s;
            
        # close bracket
        } elsif ($f =~ m|^(\))(.*)|) {
            
        } else { 
            last;
        }
        $f = $2;
    }
    
    # report
    print qq[Output: '];
    for $k (sort keys %c) {
        $c{$k} = '' if $c{$k} < 2;
        print qq[$k$c{$k}];
    }
    say q['];
}
 

27 lines of code
Completed after the closing date and not submitted to GitHub

Output from script


Input:  '((N2O)3(H2O)2)2'
Output: 'H8N12O10'

Input:  'Mg3(PO4)2'
Output: 'Mg3O8P2'

Input:  '(((H)2)3)4'
Output: 'H24'

Input:  'NaCl3(O2(S10)2)2Mg'
Output: 'Cl3MgNaO4S40'

Input:  'Z2Y3(X2W)2'
Output: 'W2X4Y3Z2'

 

Any content of this website which has been created by Peter Campbell Smith is in the public domain