Chemspell
During a recent puzzle writing session I was looking for a new mechanic to build the next puzzle around. I remembered sitting in chemistry class and staring at the periodic table on the wall. I would spend the entire lecture attempting to spell words using the atomic symbols. Despite frequently getting stuck due to the lack of ‘e’s it was a fun activity - the primary criteria for puzzle mechanics. So I decided to build a tool to generate the spellings.
How does it work? Step one gather the data. ✓
var elements = [
[1 , "H" , "Hydrogen" ],
[2 , "He", "Helium" ],
[3 , "Li", "Lithium" ],
[4 , "Be", "Beryllium" ],
[5 , "B" , "Boron" ],
[6 , "C" , "Carbon" ],
[7 , "N" , "Nitrogen" ],
[8 , "O" , "Oxygen" ],
[9 , "F" , "Fluorine" ],
[10 , "Ne", "Neon" ],
[11 , "Na", "Sodium" ],
[12 , "Mg", "Magnesium" ],
[13 , "Al", "Aluminum" ],
[14 , "Si", "Silicon" ],
[15 , "P" , "Phosphorus" ],
[16 , "S" , "Sulfur" ],
[17 , "Cl", "Chlorine" ],
[18 , "Ar", "Argon" ],
[19 , "K" , "Potassium" ],
[20 , "Ca", "Calcium" ],
[21 , "Sc", "Scandium" ],
[22 , "Ti", "Titanium" ],
[23 , "V" , "Vanadium" ],
[24 , "Cr", "Chromium" ],
[25 , "Mn", "Manganese" ],
[26 , "Fe", "Iron" ],
[27 , "Co", "Cobalt" ],
[28 , "Ni", "Nickel" ],
[29 , "Cu", "Copper" ],
[30 , "Zn", "Zinc" ],
[31 , "Ga", "Gallium" ],
[32 , "Ge", "Germanium" ],
[33 , "As", "Arsenic" ],
[34 , "Se", "Selenium" ],
[35 , "Br", "Bromine" ],
[36 , "Kr", "Krypton" ],
[37 , "Rb", "Rubidium" ],
[38 , "Sr", "Strontium" ],
[39 , "Y" , "Yttrium" ],
[40 , "Zr", "Zirconium" ],
[41 , "Nb", "Niobium" ],
[42 , "Mo", "Molybdenum" ],
[43 , "Tc", "Technetium" ],
[44 , "Ru", "Ruthenium" ],
[45 , "Rh", "Rhodium" ],
[46 , "Pd", "Palladium" ],
[47 , "Ag", "Silver" ],
[48 , "Cd", "Cadmium" ],
[49 , "In", "Indium" ],
[50 , "Sn", "Tin" ],
[51 , "Sb", "Antimony" ],
[52 , "Te", "Tellurium" ],
[53 , "I" , "Iodine" ],
[54 , "Xe", "Xenon" ],
[55 , "Cs", "Cesium" ],
[56 , "Ba", "Barium" ],
[57 , "La", "Lanthanum" ],
[58 , "Ce", "Cerium" ],
[59 , "Pr", "Praseodymium" ],
[60 , "Nd", "Neodymium" ],
[61 , "Pm", "Promethium" ],
[62 , "Sm", "Samarium" ],
[63 , "Eu", "Europium" ],
[64 , "Gd", "Gadolinium" ],
[65 , "Tb", "Terbium" ],
[66 , "Dy", "Dysprosium" ],
[67 , "Ho", "Holmium" ],
[68 , "Er", "Erbium" ],
[69 , "Tm", "Thulium" ],
[70 , "Yb", "Ytterbium" ],
[71 , "Lu", "Lutetium" ],
[72 , "Hf", "Hafnium" ],
[73 , "Ta", "Tantalum" ],
[74 , "W" , "Tungsten" ],
[75 , "Re", "Rhenium" ],
[76 , "Os", "Osmium" ],
[77 , "Ir", "Iridium" ],
[78 , "Pt", "Platinum" ],
[79 , "Au", "Gold" ],
[80 , "Hg", "Mercury" ],
[81 , "Tl", "Thallium" ],
[82 , "Pb", "Lead" ],
[83 , "Bi", "Bismuth" ],
[84 , "Po", "Polonium" ],
[85 , "At", "Astatine" ],
[86 , "Rn", "Radon" ],
[87 , "Fr", "Francium" ],
[88 , "Ra", "Radium" ],
[89 , "Ac", "Actinium" ],
[90 , "Th", "Thorium" ],
[91 , "Pa", "Protactinium" ],
[92 , "U" , "Uranium" ],
[93 , "Np", "Neptunium" ],
[94 , "Pu", "Plutonium" ],
[95 , "Am", "Americium" ],
[96 , "Cm", "Curium" ],
[97 , "Bk", "Berkelium" ],
[98 , "Cf", "Californium" ],
[99 , "Es", "Einsteinium" ],
[100, "Fm", "Fermium" ],
[101, "Md", "Mendelevium" ],
[102, "No", "Nobelium" ],
[103, "Lr", "Lawrencium" ],
[104, "Rf", "Rutherfordium"],
[105, "Db", "Dubnium" ],
[106, "Sg", "Seaborgium" ],
[107, "Bh", "Bohrium" ],
[108, "Hs", "Hassium" ],
[109, "Mt", "Meitnerium" ],
[110, "Ds", "Darmstadtium" ],
[111, "Rg", "Roentgenium" ],
[112, "Cn", "Copernicium" ],
[113, "Nh", "Nihonium" ],
[114, "Fl", "Flerovium" ],
[115, "Mc", "Moscovium" ],
[116, "Lv", "Livermorium" ],
[117, "Ts", "Tennessine" ],
[118, "Og", "Oganesson" ]
].map(([num, symbol, name]) => {
return {number: num, symbol, name};
});
Step 2, generate all possible ways to partition the target word. Atomic symbols are either one or two letters long, thus determining our partition sizes. There are two approaches we can take here, the clever one or the lazy one. The clever approach would be to dynamically build a directed, acyclic graph of potential spellings similar to a trie or regular expression. While this is an intellectually interesting exercise, in the amount of time it would take to write that code I could run the lazy approach for the entire dictionary and get back to the puzzle writing. So let’s do that instead.
The lazy approach, generate all possible partitions and toss out the ones that do not add up to the correct length. This ends up being a crossproduct and filter.1
const product = (it, repeat) => {
let argv = Array.prototype
.slice
.call(arguments)
let argc = argv.length
if (argc === 2
&& !isNaN(argv[argc - 1])) {
var copies = [];
for(let i = 0; i < repeat; i++){
copies.push(argv[0].slice())
}
argv = copies
}
return argv.reduce((acc, v) => {
let tmp = []
acc.forEach((a0) => {
v.forEach((a1) => {
tmp.push(a0.concat(a1))
})
})
return tmp
}, [[]])
}
const generatePartitions = (() => {
let memo = {}
return (wordlength) => {
if(wordlength in memo) {
return memo[wordlength]
}
let result = []
for(var i=1; i<=wordlength; i++){
result = result.concat(
product([1,2], i)
.filter(g =>
g.reduce(
(acc, el) => acc+el, 0)
== wordlength))
}
memo[wordlength] = result
return result
}
})()
Next we filter the generated partions by slicing the word and checking that each substring is an atomic symbol. The filtered partitions are the possible spellings. If no partitions survive the filter, the word is not spellable.
And now for some fun facts I discovered while experimenting with chemspell.
Fifteen, or 12.71%, of the elements are spellable - Carbon, Neon, Silicon, Phosphorus, Iron, Copper, Arsenic, Krypton, Silver, Tin, Xenon, Bismuth, Astatine, Tennessine, and Oganesson. Of these, Phosphorus has the most potential spellings with six ([P,H,Os,P,Ho,Ru,S], [P,Ho,S,P,Ho,Ru,S], [P,H,O,S,P,Ho,Ru,S], [P,H,Os,P,H,O,Ru,S], [P,Ho,S,P,H,O,Ru,S], [P,H,O,S,P,H,O,Ru,S]).
Of the 607,486 words in my computer’s dictionary 85,139, or 14.01% are spellable. ‘innocuousnesses’ has the most potential spellings with 48. Try it above for the full list.
If Wheel of Fortune were to create a special episode where all puzzles are spellable with atomic symbols they would provide Sulphur, Nitrogen, Carbon, Phosphorus, Hydrogen, and Oxygen
instead of RSTLN E
.
To maximize your odds of revealing part of the puzzle you should pick Boron, Flourine, Potassium, and Iodine
.
Or if you would like to pick a more creative strategy, here is the normalized heatmap.
- H
- He
- Li
- Be
- B
- C
- N
- O
- F
- Ne
- Na
- Mg
- Al
- Si
- P
- S
- Cl
- Ar
- K
- Ca
- Sc
- Ti
- V
- Cr
- Mn
- Fe
- Co
- Ni
- Cu
- Zn
- Ga
- Ge
- As
- Se
- Br
- Kr
- Rb
- Sr
- Y
- Zr
- Nb
- Mo
- Tc
- Ru
- Rh
- Pd
- Ag
- Cd
- In
- Sn
- Sb
- Te
- I
- Xe
- Cs
- Ba
- La
- Ce
- Pr
- Nd
- Pm
- Sm
- Eu
- Gd
- Tb
- Dy
- Ho
- Er
- Tm
- Yb
- Lu
- Hf
- Ta
- W
- Re
- Os
- Ir
- Pt
- Au
- Hg
- Tl
- Pb
- Bi
- Po
- At
- Rn
- Fr
- Ra
- Ac
- Th
- Pa
- U
- Np
- Pu
- Am
- Cm
- Bk
- Cf
- Es
- Fm
- Md
- No
- Lr
- Rf
- Db
- Sg
- Bh
- Hs
- Mt
- Ds
- Rg
- Cn
- Nh
- Fl
- Mc
- Lv
- Ts
- Og
Oh yeah. The puzzle. That was the whole point wasn’t it. I guess you will have to wait for that one.
- Turns out I couldn’t bring myself to be completely lazy and added memoization to this implementation. Still not elegant or efficient, but avoids stack overflows.