Find the Mr of a Given Compound!
Jelly, 63 bytes
ḟØDOP%⁽¡ṛị“ÇṚÆ’BH+“Ḳ"ɦṀ⁷6<s¡_-¦y⁼Ḟ¡¡FPɓ‘¤÷5
fØDVȯ1×Ç
Œs>œṗ⁸ḊÇ€S
A monadic link accepting a list of characters and returning a number.
Try it online!
How?
ḟØDOP%⁽¡ṛị“ÇṚÆ’BH+“Ḳ"ɦṀ⁷6<s¡_-¦y⁼Ḟ¡¡FPɓ‘¤÷5 - Link 1, Atomic weight: list of characters
- e.g. "Cl23"
ØD - digit yield = "0123456789"
ḟ - filter discard "Cl"
O - cast to ordinals [67,108]
P - product 7236
⁽¡ṛ - base 250 literal = 1223
% - modulo 1121
¤ - nilad followed by link(s) as a nilad:
“ÇṚÆ’ - base 250 literal = 983264
B - convert to binary = [ 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0]
H - halve = [ 0.5, 0.5, 0.5, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0.5, 0, 0, 0, 0, 0]
“Ḳ"ɦṀ⁷6<s¡_-¦y⁼Ḟ¡¡FPɓ‘ - code-page indexes = [177 , 34 , 160 , 200 , 135, 54, 60, 115, 0, 95, 45, 5, 121 , 140 , 195 , 0, 0, 70, 80, 155]
+ - addition = [177.5, 34.5, 160.5, 200.5, 135, 54, 60, 115, 0, 95, 45, 5, 121.5, 140.5, 195.5, 0, 0, 70, 80, 155]
ị - index into (1-indexed and modular)
- ...20 items so e.g. 1121%20=1 so 177.5
÷5 - divide by 5 35.5
fØDVȯ1×Ç - Link 2: Total weight of multiple of atoms: list of characters e.g. "Cl23"
ØD - digit yield = "0123456789"
f - filter keep "23"
V - evaluate as Jelly code 23
ȯ1 - logical or with one (no digits yields an empty string which evaluates to zero)
Ç - call last link (1) as a monad (get the atomic weight) 35.5
× - multiply 816.5
Œs>œṗ⁸ḊÇ€S - Main link: list of characters e.g. "C24HCl23"
Œs - swap case "c24hcL23"
> - greater than? (vectorises) 10011000
⁸ - chain's left argument "C24HCl23"
œṗ - partition at truthy indexes ["","C24","H","Cl23"]
Ḋ - dequeue ["C24","H","Cl23"]
Ç€ - call last link (2) as a monad for €ach [ 288, 1, 816.5]
S - sum 1105.5
Python 3, 189 182 168 bytes
-14 bytes by using the hash from Justin Mariner's JavaScript (ES6) answer.
import re
lambda s:sum([[9,35.5,39.1,24.3,28.1,14,16,31,40.1,23,32.1,10.8,12,27,6.9,19,0,1][int(a,29)%633%35%18]*int(n or 1)for a,n in re.findall("(\D[a-z]?)(\d*)",s)])
Try it online!
Below is the 182 byte version, I'll leave the explanation for this one - the above just changes the order of the weights, uses int
to convert the element name from base 29
, and uses different dividends to compress the range of integers down - see Justin Mariner's answer.
import re
lambda s:sum([[16,31,40.1,32.1,0,24.3,12,39.1,28.1,19,0,9,10.8,23,27,35.5,6.9,14,1][ord(a[0])*ord(a[-1])%1135%98%19]*int(n or 1)for a,n in re.findall("(\D[a-z]?)(\d*)",s)])
An unnamed function accepting a string, s
, and returning a number.
Try it online!
How?
Uses a regex to split the input, s
, into the elements and their counts using:
re.findall("(\D[a-z]?)(\d*)",s)
\D
matches exactly one non-digit and [a-z]?
matches 0 or 1 lowercase letter, together matching elements. \d*
matches 0 or more digits. The parentheses make these into two groups, and as such findall("...",s)
returns a list of tuples of strings, [(element, number),...]
.
The number is simple to extract, the only thing to handle is that an empty string means 1, this is achieved with a logical or
since Python strings are falsey: int(n or 1)
.
The element string is given a unique number by taking the product of its first and last character's ordinals (usually these are the same e.g. S or C, but we need to differentiate between Cl, C, Ca, and Na so we cannot just use one character).
These numbers are then hashed to cover a much smaller range of [0,18], found by a search of the modulo space resulting in %1135%98%19
. For example "Cl"
has ordinals 67
and 108
, which multiply to give 7736
, which, modulo 1135
is 426
, which modulo 98
is 34
, which modulo 19
is 15
; this number is used to index into a list of integers - the 15th (0-indexed) value in the list:
[16,31,40.1,32.1,0,24.3,12,39.1,28.1,19,0,9,10.8,23,27,35.5,6.9,14,1]
is 35.5
, the atomic weight of Cl, which is then multiplied by the number of such elements (as found above).
These products are then added together using sum(...)
.
PHP, 235 bytes
preg_match_all("#([A-Z][a-z]?)(\d*)#",$argn,$m);foreach($m[1]as$v)$s+=array_combine([H,Li,Be,B,C,N,O,F,Na,Mg,Al,Si,P,S,Cl,K,Ca],[1,6.9,9,10.8,12,14,16,19,23,24.3,27,28.1,31,32.1,35.5,39.1,40.1])[$v]*($m[2][+$k++]?:1);printf("%.1f",$s);
Try it online!
Instead of array_combine([H,Li,Be,B,C,N,O,F,Na,Mg,Al,Si,P,S,Cl,K,Ca],[1,6.9,9,10.8,12,14,16,19,23,24.3,27,28.1,31,32.1,35.5,39.1,40.1])
you can use [H=>1,Li=>6.9,Be=>9,B=>10.8,C=>12,N=>14,O=>16,F=>19,Na=>23,Mg=>24.3,Al=>27,Si=>28.1,P=>31,S=>32.1,Cl=>35.5,K=>39.1,Ca=>40.1]
with the same Byte count