perl logo Perl logo (Thanks to Olaf Alders)

The weekly challenge 387 - Task 2: Rational Numbers

  1 #!/usr/bin/env perl
  2 # https://theweeklychallenge.org/blog/perl-weekly-challenge-387/#TASK2
  3 #
  4 # Task 2: Rational Numbers
  5 # ========================
  6 #
  7 # You are given a chemical formula with elements, numbers, and parentheses.
  8 #
  9 # Write a script to count the total number of each type of atom by expanding
 10 # all grouped multipliers. Then, format and return the final inventory as a
 11 # single string sorted alphabetically by element name, including the total
 12 # count only if it is greater than 1.
 13 #
 14 ## Example 1
 15 ##
 16 ## Input: $formula = "((N2O)3(H2O)2)2"
 17 ## Output: "H8N12O10"
 18 ##
 19 ## Step 1: Expand the innermost parentheses
 20 ##     (N2O)3 => N = 2*3 = 6, O = 1*3 = 3 => N6O3
 21 ##     (H2O)2 => H = 2*2 = 4, O = 1*2 = 2 => H4O2
 22 ##
 23 ## Step 2: Combine inside the outer parentheses
 24 ##     Formula becomes: (N6O3 H4O2)2
 25 ##     Sum up identical elements inside: (N6 H4 O5)2
 26 ##
 27 ## Step 3: Apply the outer multiplier
 28 ##     N = 6*2 = 12
 29 ##     H = 4*2 = 8
 30 ##     O = 5*2 = 10
 31 ##
 32 ## Step 4: Sort alphabetically and format
 33 ##     Alphabetical order: H, N, O
 34 ##     Counts: H: 8, N: 12, O: 10
 35 #
 36 ## Example 2
 37 ##
 38 ## Input: $formula = "Mg3(PO4)2"
 39 ## Output: "Mg3O8P2"
 40 ##
 41 ## Step 1: Parse ungrouped elements
 42 ##     Mg3 => Mg = 3
 43 ##
 44 ## Step 2: Expand parentheses (PO4)2
 45 ##     P = 1*2 = 2
 46 ##     O = 4*2 = 8
 47 ##
 48 ## Step 3: Total up counts
 49 ##     Mg = 3
 50 ##     P  = 2
 51 ##     O  = 8
 52 ##
 53 ## Step 4: Sort alphabetically and format
 54 ##     Alphabetical order: Mg, O, P
 55 ##     Counts: Mg: 3, O: 8, P: 2
 56 #
 57 ## Example 3
 58 ##
 59 ## Input: $formula = "(((H)2)3)4"
 60 ## Output: "H24"
 61 ##
 62 ## Step 1: Expand innermost level (H)2
 63 ##     H = 1*2 = 2 => formula becomes ((H2)3)4
 64 ##
 65 ## Step 2: Expand middle level (H2)3
 66 ##     H = 2*3 = 6 => formula becomes (H6)4
 67 ##
 68 ## Step 3: Expand outer level (H6)4
 69 ##     H = 6*4 = 24
 70 ##
 71 ## Step 4: Sort alphabetically and format
 72 ##     Single element: H: 24
 73 #
 74 ## Example 4
 75 ##
 76 ## Input: $formula = "NaCl3(O2(S10)2)2Mg"
 77 ## Output: "Cl3MgNaO4S40"
 78 ##
 79 ## Step 1: Expand innermost parentheses (S10)2
 80 ##     S = 10*2 = 20 => inner formula becomes => O2S20
 81 ##
 82 ## Step 2: Expand outer parentheses (O2S20)2
 83 ##     O = 2*2  = 4
 84 ##     S = 20*2 = 40
 85 ##
 86 ## Step 3: Combine all parts
 87 ##     Ungrouped start: Na (Na = 1), Cl3 (Cl = 3)
 88 ##     Expanded middle: O = 4, S = 40
 89 ##     Ungrouped end: Mg (Mg = 1)
 90 ##
 91 ## Step 4: Sort alphabetically and format
 92 ##     Alphabetical order: Cl (3), Mg (1), Na (1), O (4), S (40)
 93 ##     Omit the number 1 for Mg and Na.
 94 #
 95 ## Example 5
 96 ##
 97 ## Input: $formula = "Z2Y3(X2W)2"
 98 ## Output: "W2X4Y3Z2"
 99 ##
100 ## Step 1: Parse ungrouped elements
101 ##     Z2 => Z = 2
102 ##     Y3 => Y = 3
103 ##
104 ## Step 2: Expand parentheses (X2W)2
105 ##     X = 2*2 = 4
106 ##     W = 1*2 = 2
107 ##
108 ## Step 3: Total up counts
109 ##     W = 2, X = 4, Y = 3, Z = 2
110 ##
111 ## Step 4: Sort alphabetically and format
112 ##     Alphabetical order: W (2), X (4), Y (3), Z (2)
113 #
114 ############################################################
115 ##
116 ## discussion
117 ##
118 ############################################################
119 #
120 # As long as the formula still contains "(" and ")\d+" with
121 # no "(" or ")" in between, we calculate the replacement
122 # for that part. Once all of that is done, we calculate how
123 # often each element appears in this result and sort by
124 # element name.
125 
126 use v5.36;
127 
128 rational_numbers("((N2O)3(H2O)2)2");
129 rational_numbers("Mg3(PO4)2");
130 rational_numbers("(((H)2)3)4");
131 rational_numbers("NaCl3(O2(S10)2)2Mg");
132 rational_numbers("Z2Y3(X2W)2");
133 
134 sub rational_numbers($formula) {
135    say "Input: \"$formula\"";
136    while($formula =~ m/\([^\(\)]*\)\d+/) {
137       my $found = $&;
138       my $replacement = $found;
139       $replacement =~ s/\(//;
140       my ($elements, $count) = split /\)/, $replacement;
141       $replacement = "";
142       while($elements =~ m/([A-Z][a-z]{0,1})(\d*)/) {
143          my $next_elem = $&;
144          my ($elem_name, $elem_count) = ($1, $2);
145          $elements =~ s/\Q$&\E//;
146          $replacement .= "$elem_name";
147          if(length($elem_count)) {
148             $replacement .= ( $count * $elem_count );
149          } else {
150             $replacement .= $count;
151          }
152       }
153       $formula =~ s/\Q$found\E/$replacement/;
154    }
155    my $all_elems = {};
156    while($formula =~ m/([A-Z][a-z]{0,1})(\d*)/) {
157       my $next_elem = $&;
158       my ($elem_name, $elem_count) = ($1, $2);
159       $elem_count ||= 1;
160       $formula =~ s/\Q$&\E//;
161       $all_elems->{$elem_name} += $elem_count;
162    }
163    print "Output: \"";
164    foreach my $key (sort keys %$all_elems) {
165       print $key;
166       print $all_elems->{$key} if $all_elems->{$key} > 1;
167    }
168    say "\"";
169 }