10
\$\begingroup\$

Back when I was a freshman in highschool taking chemistry, I'd look at the periodic table of elements and spell dirty words out with the number of the elements (HeCK would be 2619, 2-6-19).

I was thinking about this the other day when I saw an amazing shirt that spelled out BeEr (4-68)

So my codegolf challenge is shortest program to output a list of words you can spell with the periodic table of elements AND the number code that would represent that word.

/usr/share/dict/words or whatever dictionary you want to use for the word list. If you're using a "non-standard" word list, let us know what it is!

\$\endgroup\$
9
  • \$\begingroup\$ 'The' number code? What about cases where there's more than one? E.g. CO vs Co. \$\endgroup\$ Jun 9, 2012 at 7:13
  • 3
    \$\begingroup\$ As I'm reading the responses below, I noticed one place that everybody could cut out a few characters. They might remove Co, Si, Sc, Os, Hs, Po, Pb, Np, No, Yb, Cs, and maybe others from their list of elements, since they can all be constructed from other elements. \$\endgroup\$
    – PhiNotPi
    Jun 9, 2012 at 12:35
  • 1
    \$\begingroup\$ Not Ytterbium, that's my favorite element! \$\endgroup\$
    – Rob
    Jun 9, 2012 at 15:22
  • 2
    \$\begingroup\$ Just to clarify, the elements that I listed can always be safely removed. For example, Ytterbium can always be replaced with a Yttrium and a Boron, no matter what language the word list is in. \$\endgroup\$
    – PhiNotPi
    Jun 9, 2012 at 21:00
  • 1
    \$\begingroup\$ I'm not quite sure whether I understand the task completely: Shall we find matching words for the elements until every element was printed, or shall we print every word from the dict, which can be combined from the element table? Or something else? \$\endgroup\$ Jun 10, 2012 at 3:50

5 Answers 5

6
\$\begingroup\$

GolfScript (339 303 302 301 294 chars)

n/{{{32|}%}:L~['']{{`{\+}+'HHeLiBeBCNOFNeNaMgAl
PSClArKCa TiVCrMnFe


ZnGaGeAsSeBrKrRbSrYZr
MoTcRuRhPdAgCd


TeIXe
BaLaCePrNdPmSmEuGdTbDy
ErTm
Lu
TaWRe
IrPtAuHgTl


AtRnFrRaAcThPaU

AmCm

EsFmMd
LrRfDbSg

MtDsRg
UutFl
Lv'{[1/{.0=96>{+}*}/]}:S~:^/}%.{L}%2$?.){=S{^?}%`+p 0}{;{L.,2$<=},.}if}do}%;

With credit to PhiNotPi whose observation on unnecessary elements allowed me to save 33 chars.

This is IMO much more idiomatic GolfScript than the previous recursive approach.

Note that I allow words in the dictionary to be mixed case (L is a function to lower-case text on the assumption that it doesn't matter if non-alpha characters get broken) but reject any with apostrophes or accents.

Since this is code golf, I've optimised for code length rather than speed. This is horrendously slow. It expects the word list to be supplied at stdin and outputs to stdout in the format:

"ac[89]"
"accra[89 6 88]"
"achebe[89 2 4]"
...

(lower-casing any mixed-case input words for which it finds a match).

If you're more interested in the elements than the numbers per se, for the low low price of 261 253 chars you can use

n/{{{32|}%}:L~['']{{`{\+}+'HHeLiBeBCNOFNeNaMgAlPSClArKCaTiVCrMnFeZnGaGeAsSeBrKrRbSrYZrMoTcRuRhPdAgCdTeIXeBaLaCePrNdPmSmEuGdTbDyErTmLuTaWReIrPtAuHgTlAtRnFrRaAcThPaUAmCmEsFmMdLrRfDbSgMtDsRgUutFlLv'[1/{.0=96>{+}*}/]/}%.{L}%2$?.){=p 0}{;{L.,2$<=},.}if}do}%;

which gives output like

"Ac"
"AcCRa"
"AcHeBe"
...
\$\endgroup\$
1
  • \$\begingroup\$ Take the extra character out, " doesn't bother me at all. And of course peter taylor comes in and blows everyone away with golfscript. \$\endgroup\$
    – Rob
    Jun 9, 2012 at 15:23
3
\$\begingroup\$

Ruby - 547 393

New version, thanks for the suggestions:

e='HHeLiBeBCNOFNeNaMgAlSiPSClArKaCaScTiVCrMnFeCoNiCuZnGaGeAsSeBrKrRbSrYZrNbMoTcRuRhPdAgCdInSnSbTeIXeCsBaLaCePrNdPmSmEuGdTbDyHoErTmYbLuHfTaWReOsIrPtAuHgTlPbBiPoAtRnFrRaAcThPaUNpPuAmCmBkCfEsFmMdNoLrRfDbSgBhHsMtDsRgCnUutFlUupLvUusUuo'.scan(/[A-Z][a-z]*/).map &:upcase
r="(#{e.join ?|})"
$<.each{|w|(i=0;i+=1 until w=~/^#{r*i}$/i
$><<w;p$~.to_a[1..-1].map{|m|e.index(m.upcase)+1})if w=~/^#{r}+$/i}

e=%w{h he li be b c n o f ne na mg al si p s cl ar ka 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 uut fl uup lv uus uuo}
x = "(#{e.join(?|)})"
regex = /^#{x}+$/i
File.foreach('/usr/share/dict/words'){|w|
if w=~/^#{x}+$/i
puts w
i=1
i+=1 until w=~/^#{x*i}$/i 
puts $~[1..-1].map{|m|e.index(m.downcase)+1}.join ?-
end
}

uses regexes. slow, and much room for improvement but i must go now :-)

\$\endgroup\$
2
  • 1
    \$\begingroup\$ 1) You can spare storage by using Peter Taylor's trick (as in his original code): e='HHeLiBe...LvUusUuo'.scan(/[A-Z][a-z]*/).map &:downcase. 2) Variable regex is never used. 3) Read the words from standard input: $<.each{|w|.... With these modification the code got reduced to 410 characters. \$\endgroup\$
    – manatwork
    Jun 15, 2012 at 6:40
  • \$\begingroup\$ I think that you can apply the same space-saving approach for unnecessary elements too, at the cost of adding two characters to the scan regex. Use space if you don't like newlines - I'm using newlines mainly so that it's not necessary to scroll to see the main loop. \$\endgroup\$ Jun 15, 2012 at 13:54
2
\$\begingroup\$

Python 710 (357 + 261 + 92)

e=". 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 uut fl uup lv uus uuo".split()

i=e.index
def m(w,p=("",[])):
 if not w:return p
 x,y,z=w[0],w[:2],w[:3]
 if x!=y and y in e:
    a=m(w[2:],(p[0]+y,p[1]+[i(y)]))
    if a:return a
 if x in e:
    b=m(w[1:],(p[0]+x,p[1]+[i(x)]))
    if b:return b
 if z in e:
    c=m(w[3:],(p[0]+z,p[1]+[i(z)]))
    if c:return c

f=open('/usr/share/dict/words','r')
for l in f:
 x=m(l[:-1])
 if x:print x[0],x[1]
f.close()

There's sure to be room for improvement in there somewhere. It's also worth noting that the second level of indentation uses the tab character.

It takes just over 5 seconds (on my computer) to go through the whole dictionary, producing output like this:

acaciin [89, 89, 53, 49]
acacin [89, 89, 49]
acalycal [89, 13, 39, 6, 13]
...

By adding another 18 characters, you can get output with the right capitalization:

e=". 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 Uut Fl Uup Lv Uus Uuo".split()

i=e.index
def m(w,p=("",[])):
 if not w:return p
 w=w.capitalize()
 x,y,z=w[0],w[:2],w[:3]
 if x!=y and y in e:
    a=m(w[2:],(p[0]+y,p[1]+[i(y)]))
    if a:return a
 if x in e:
    b=m(w[1:],(p[0]+x,p[1]+[i(x)]))
    if b:return b
 if z in e:
    c=m(w[3:],(p[0]+z,p[1]+[i(z)]))
    if c:return c

OUTPUT:

AcAcIIn [89, 89, 53, 49]
AcAcIn [89, 89, 49]
AcAlYCAl [89, 13, 39, 6, 13]
...

You can also check individual words:

>>> m("beer")
('beer', [4, 68])
\$\endgroup\$
1
  • \$\begingroup\$ Can you add the one that outputs proper capitalization? I think that's pretty neat and I'm pretty new to python. \$\endgroup\$
    – Rob
    Jun 9, 2012 at 15:26
0
\$\begingroup\$

Python - 1328 (975 + 285 chars of code + 68 dictionary code)

t1={'c':6,'b':5,'f':9,'i':53,'h':1,'k':19,'o':8,'n':7,'p':15,
's':16,'u':92,'w':74,'v':23,'y':39}
t2={'ru':44,'re':75,'rf':104,'rg':111,'ra':88,'rb':37,
'rn':86,'rh':45,'be':4,'ba':56,'bh':107,'bi':83,
'bk':97,'br':35,'os':76,'ge':32,'gd':64,'ga':31,
'pr':59,'pt':78,'pu':94,'pb':82,'pa':91,'pd':46,
'cd':48,'po':84,'pm':61,'hs':108,'ho':67,'hf':72,
'hg':80,'he':2,'md':101,'mg':12,'mo':42,'mn':25,
'mt':109,'zn':30,'eu':63,'es':99,'er':68,'ni':28,
'no':102,'na':11,'nb':41,'nd':60,'ne':10,'np':93,
'fr':87,'fe':26,'fl':114,'fm':100,'sr':38,'kr':36,
'si':14,'sn':50,'sm':62,'sc':21,'sb':51,'sg':106,
'se':34,'co':27,'cn':112,'cm':96,'cl':17,'ca':20,
'cf':98,'ce':58,'xe':54,'lu':71,'cs':55,'cr':24,
'cu':29,'la':57,'li':3,'lv':116,'tl':81,'tm':69,
'lr':103,'th':90,'ti':22,'te':52,'tb':65,'tc':43,
'ta':73,'yb':70,'db':105,'dy':66,'ds':110,'at':85,
'ac':89,'ag':47,'ir':77,'am':95,'al':13,'as':33,
'ar':18,'au':79,'zr':40,'in':49}
t3={'uut':113,'uuo':118,'uup':115,'uus':117}
def p(s):
 o=0;b=0;a=[];S=str;l=S.lower;h=dict.has_key;L=len
 while o<L(s):
  D=0
  for i in 1,2,3:exec('if h(t%d,l(s[o:o+%d])) and b<%d:a+=[S(t%d[s[o:o+%d]])];o+=%d;b=0;D=1'%(i,i,i,i,i,i))
  if D==0:
   if b==3 or L(a)==0:return
   else:b=L(S(a[-1]));o-=b;a.pop()
 return '-'.join(a)

For the dictionary part:

f=open(input(),'r')
for i in f.readlines():print p(i[:-1])
f.close()
\$\endgroup\$
2
  • \$\begingroup\$ It is really shorter to use an explicit hash initialisation than to use an explicit array initialisation and turn it into an index-of hash? \$\endgroup\$ Jun 9, 2012 at 9:31
  • \$\begingroup\$ I just used a dictionary for ease of use. Having an array of tuples would be a little more character costly. Although ordering the elements would be a good idea... \$\endgroup\$
    – beary605
    Jun 9, 2012 at 14:02
0
\$\begingroup\$

C, 775 771 chars

char*e[]={"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","uut","fl","uup","lv","uus","uu",0};
b[99],n;
c(w,o,l)char*w,*o,**l;{
    return!*w||!strncmp(*l,w,n=strlen(*l))&&c(w+n,o+sprintf(o,",%d",l-e+1),e)||*++l&&c(w,o,l);
}
main(){
    while(gets(b))c(b,b+9,e)&&printf("%s%s\n",b,b+9);
}

Input: Word per line, must be lowercase. usr/share/dict/words is fine.
Output: Word and numbers, e.g.: acceptances,89,58,15,73,7,6,99

Logic:
c(w,o,l) checks the word w, starting with element l.
Two-way recursion is used - if the first element matches the head of the element list, check the remainer of w against the full element list. If this match fails, check the word against the tail of the list.
The buffer o accumulates the element numbers along the successful path. After a match, it will contain the list of numbers, and is printed.

Issues:
The list isn't encoded efficiently - too much " and ,". But this way it's easy to use. I'm sure it can be much improved, without too much cost in code.

\$\endgroup\$

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