37
\$\begingroup\$

Introduction

Unlike English, German is considered to have quite a phonemic writing system. That means that the correspondence between spelling and pronunciation is close. Given any word you aren't familiar with, you would still know how to pronounce it because of the spelling system. This means a computer should be able to do it too right?

Challenge

Write a program or function that takes as input a string representing a German word, and prints or returns its pronunciation in the International Phonetic Alphabet (IPA).

I am of course not going to make you learn German or the full IPA. This one Wikipedia section provides almost all the German to IPA rules you need, and I've coded an ungolfed C# reference implementation.

Also provided in that link is a list of 400 common German words and their IPA pronunciation (needed for validation). Taking an example from that list, if the input is solltest, the correct output is ˈzɔltəst.

The reference implementation adds two helpful rules not mentioned in the Wikipedia section: It assumes word stress is on first syllable (very likely in German), and uses a better heuristic for determining when the letter "e" represents the schwa sound /ə/. It also implements special processing for prefixes, but that didn't improve results as much as I thought it would.

Details

To be considered a valid entry, your program must meet the following requirements:

  • Your IPA output must be an exact match for at least 300 of the 400 words in the reference word list (the reference implementation gets 333 correct)
  • Your program must make a guess for any plausibly German word. So we have a technical requirement, this will mean that for any input that matches the regex [a-zA-ZäÄöÖüÜ][a-zäöüß]* and has at least one vowel (aeiouyäöü), you must produce non-whitespace-only output and not error out.
  • The program must be deterministic (always produce the same output given the same input)
  • Otherwise, standard loopholes are forbidden (especially the one about fetching off-site resources)

Miscellaneous things you are allowed to do:

  • Have leading and trailing whitespace in your output if you must
  • Use any pre-existing character encoding in the output (I can't imagine anything other than Unicode working well, but if you can, congrats)
  • Assume the input is in some normalized form like Unicode normalization forms NFD, NFC, etc. For example, is ä written as a single character or a base character + a combining character?
  • Use standard input and output methods

Scoring & IPA Characters

Scoring is in bytes. Be warned that the German characters and the IPA characters are 2 bytes in UTF-8. Also, the IPA character U+0327 COMBINING INVERTED BREVE BELOW ( ̯) is a Unicode combining character and is a 2 byte UTF-8 character on its own. That means something like ɐ̯ would count as 4 bytes in UTF-8. For the curious, this symbol means the vowel does not form the syllable nucleus (the previous one does instead).

Also, beware of these IPA characters that in some fonts look like other ASCII characters: ɡ, ɪ, ʏ, ː (marks a long vowel), ˈ (marks which syllable has the stress in a multisyllable word).

How the reference word list was created

This section is extra info not needed for the challenge.

The word list was grabbed from this Wiktionary word frequency list, removing repeats because of casing difference and two words that didn't have German entries in the English Wiktionary (oh & hey). The IPA was from looking at both the English and German Wiktionaries. Where multiple pronunciations were offered, I chose the more formal and standard one. If this wasn't clear, I chose the one that most fit the general rules.

I also had to standardize how the letter "r" is pronounced. It is heavily dependent on the region how this letter pronounced, and Wiktionary was not at all consistent in which one it chose. I felt it tended towards the following: "r" is pronounced /ɐ̯/ when followed by a long vowel and a vowel does not follow, otherwise, it is ʁ. So, I changed all of them to follow that rule, except for the ver- and er- prefixes that were quite consistently /(f)ɛɐ̯/. Similarly, I standardized "eu" as /ɔʏ̯/.

\$\endgroup\$
14
  • 16
    \$\begingroup\$ Mathematica has a built-in for this (#~WordData~"PhoneticForm"&), but it works only for English words. \$\endgroup\$ Commented May 15, 2017 at 23:12
  • 30
    \$\begingroup\$ @JungHwanMin I read your comment as follows: Immediate heart attack, sigh of relief. \$\endgroup\$
    – DPenner1
    Commented May 15, 2017 at 23:35
  • 1
    \$\begingroup\$ How should I know "gestern" is pronounced "GHES-tern" instead of "ge-SHTERN"? "bester" as "BEST-er" not "be-SHTER"? \$\endgroup\$
    – Leaky Nun
    Commented May 16, 2017 at 8:15
  • \$\begingroup\$ @LeakyNun There's no 100% algorithm for this, but your implementation only needs to get 75%. My reference implementation also gets those words wrong. \$\endgroup\$
    – DPenner1
    Commented May 16, 2017 at 12:01
  • \$\begingroup\$ @LeakyNun Well, your program will also be able to deal with German dialects, so it is more powerful. \$\endgroup\$
    – P. Siehr
    Commented Jun 22, 2017 at 15:30

1 Answer 1

10
\$\begingroup\$

PHP, 3311 2988 2916 2845 2759 2671 2667 2509 2484 bytes, passing 301/400

<?$f=fopen(__FILE__,r);fseek($f,__COMPILER_HALT_OFFSET__);eval(strtr(stream_get_contents($f),[F=>'=>',M=>'==','&'=>'&&',H=>'function ',A_=>'array',G=>'if',4=>'for','|'=>'||','~'=>'))','%'=>str,7=>'=$',8=>'[]',9=>'!$','@'=>'count(','#'=>';$',5=>'return ',3=>':(']));__halt_compiler();define(J,[ieh,ah,aa,Ah,eh,ee,ie,ih,oh,oo,Oh,uh,Uh,au,eu,Au,ei,ai,ey,ay,a,e,i,o,u,A,O,U,y])#b7e=8;Hv($a){5in_A_($a,J);}Hn($a){5!v($a);}Hpronounce($w){global$b,$e#w=%tr(%tolower(%tr($w,[ßF1,ÄF2,äF2,ÖF0,öF0,ÜF6,üF6]~,[1FS,2FA,0FO,6FU])#W=8#L7w;while($L)4each(A__merge([tzsch,dsch,tsch,zsch,sch,chs,ch,ck,dt,ng,nk,pf,ph,qu,ss,th,tz,b,c,d,f,g,h,j,k,l,m,n,p,r,s,S,t,v,w,x,z],J)as$c){$l=%len($c);G(sub%($L,0,$l)M$c){$W87c#L=sub%($L,$l);break;}}$s=8#l=@$W);4($o7t7i=0#i<$l#i++){$c7W[$i]#p7i?$W[$i-1]:0#n7iM$l-1?0:$W[$i+1];G(9n|!(n($c)&$cM$n&n($W[$i+2]~)$s[$o]87c;G($p&((9n&v($c)&n($p~|(n($n)&v($W[$i+2]~~$s[++$o]=8;}$s[@$s)-1]|A__pop($s);4each($s as$z){$b87t#t+=@$z)#e87t;}$o=[sieFziQ,duFduQ,'die'FdiQ,derFdeQT,zuFtsuQ,wirFviQT,mirFmiQT,denFdeQn,dassFdas,erFeQT,soFzoQ,warFvaQT,fürFfyQT,jaFjaQ,wieFviQ,dirFdiQT,nurFnuQT,demFdeQm,ihnFiQn,auchFaUBx,ihrFiQT,daFdaQ,schonFʃoQn,wennFvEn,malFmaQl,gutFguQt,nachFnaQx,willFvIl,mussFmUs,habFhaQp,vorFfoQT,ihmFmiQm,tunFtuQn][$w]?:'';G($o)goto x#P7B7S7V7Z=0;@$s)>1&$o=[verFfET,daFda,geFgC][join($s[0])]#o&$P=1&A__shGt($s);(($P|@$s)M2)&$o.=W)|(@$s)>2&$d=1&$h=(int)@$s)/2)#w=A__merge(...$s);4each($w as$l){G(0<$S--)continue#n7w[$B+1]#p7w[$B-1]#f=''#Z+=in_A_($B,$b)#f7lMd&9n?t3$lMb&(9n|$nMt)?p3$lMg&(9n|$nMt)?((9n&$pMi)?K:k)3$lMc?(($nMA|$nMe|$nMi)?ts:k)3$lMch?(($pMa|$pMo|$pMu)?x:K)3$lMchs|$lMx?ks3$lMck?k3$lMdsch?dZ3$lMdt|$lMth?t3$lMph|$lMv?f39f&$lMg?g3$lMh?(v($n)?h:'')3$lMng?N3$lMnk?Nk3$lMqu?kv3$lMr?((!v($n)&9nMt)?T:R)3$lMsch?S3$lMss|$lMS?s3$lMtsch|$lMtzsch|$lMzsch?tS3$lMtz|$lMz?ts3$lMw?v3$lMs?(9p&($nMp|$nMt~?S3v($n)?z:s):$f~~~~~~~~~~)#U=0;G(v($l~{G(%len($l)>1)($f=[auFaUB,euFcYB,eiFaIB][$l])|$U=1;else{G(n($n)&((9w[$B+2]&$n!=n)|v($w[$B+2]~)$U=1;G($lMe){$U=9n?:$U;G(9w[$B+2]){G($nMr)($f=A)&$U=9S=1;G($nMm|$nMl)$f=C;}}elseG($nMch)$U=0;G(in_A_($B,$e~$U=0;}$f=($U|9Z)&9f?($l[0]MO?D3$l[0]MU?y3$l[0]MA?E:$l[0]~).Q39f?($lMe?((9n|(9w[$B+2]&($nMn|$nMs~)?C:E)3$lMA?E3$lMi?I3$lMo?c3$lMu?U3($lMU|$lMy)?Y:$l~~~:$f)#V++;}$f7f?:$l;G($d&$ZM$h)$f.=W#o.7f#B++;}G(%pos($o,W)M=false&$V>1)$o=W.$o;4(#j++<%len($o);)G($o[$j]M$o[$j+1])$o=sub%($o,0,$j).sub%($o,$j+1);x:5%tr($o,[SFʃ,ZFʒ,KFç,gFɡ,NFŋ,QF'ː',WFˈ,TFɐ̯,BF'̯',RFʁ,AFɐ,EFɛ,OFœ,IFɪ,YFʏ,UFʊ,cFɔ,CFə,DFø]);}

Defines pronounce(string $word).

Usage:

assert(
    pronounce('darüber') == "daˈʁyːbɐ"
);

One note: 3 prefixes and 33 words are hard-coded, and some of the code is mildly optimized towards the testing list.

Testing code is here, though it does depend on this file.

To test:

php test.php all

Powered by the tears of ternary statements.

EDIT 7: Squeezed off ~170 bytes by writing a preprocessor into the program. As a consequence, the actual program (everything after __halt_compiler();) is kinda hard to read. If you want the unprocessed program, switch eval with print in the third statement.

\$\endgroup\$
1
  • \$\begingroup\$ This is actually 2667 bytes, not 2671 (assuming UTF-8) \$\endgroup\$ Commented Oct 13, 2017 at 19:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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