# Zahlen auf Deutsch! (Numbers in German)

Your task is to write a program that receives a base 10 number from 0 to 99 and outputs the German name of that number with non-ASCII characters replaced with ASCII equivalents.

## German number names

0 -> null
1 -> eins
2 -> zwei
3 -> drei
4 -> vier
5 -> fuenf
6 -> sechs
7 -> sieben
8 -> acht
9 -> neun
10 -> zehn
11 -> elf
12 -> zwoelf
13 -> dreizehn
14 -> vierzehn
15 -> fuenfzehn
16 -> sechzehn
17 -> siebzehn
18 -> achtzehn
19 -> neunzehn
20 -> zwanzig
21 -> einundzwanzig
22 -> zweiundzwanzig
23 -> dreiundzwanzig
24 -> vierundwanzig
30 -> dreissig
31 -> einunddreissig
40 -> vierzig
41 -> einundvierzig
50 -> fuenfzig
60 -> sechzig
70 -> siebzig
80 -> achtzig
90 -> neunzig


TL;DR - 0-12 are irregular, 13-19 are mostly just the number plus zehn, multiples of ten are mostly the number plus zig, and other numbers are the number, und, and then the multiple of ten. There are some irregularities, though.

## Rules

• This is code golf, so the lowest byte count wins.
• Standard loopholes apply.
• Any standard input/output methods are allowed.
• You may use any case you want, as long as it's consistent across different runs.

## Test Cases

3 -> drei
12 -> zwoelf
14 -> vierzehn
16 -> sechzehn
30 -> dreissig
52 -> zweiundfuenfzig
90 -> neunzig

• @TheThonnu No, the output has to be ASCII. Apr 9 at 17:40
• The real question here is... are those Zahlen or Nummern? :-P Apr 9 at 19:11
• @LuisMendo Good question :P Let's just call them Folgen von Ziffern and be safe. Apr 9 at 19:21
• Do Welsh numerals next! 20 is the highest number with a name below 100. Apr 10 at 20:10
• @gotube That's an interesting number system. Apr 11 at 7:28

# JavaScript (ES6), 213 bytes

f=(n,m=8,u=n%10)=>n<20?null ein5 zw26 drei vier fuenf sech7 sieb89 acht neun zehn elf zwoelf.replace(/\d/g,n=>['enasisn'[(m^n)%10]]).split [~~n]||f(u,0)+'zehn':(u?f(u,2)+'und':'')+f(n/=10,0)+(n^3?'zig':'ssig')


Try it online! (test cases)
Try it online! (all values from 0 to 99)

### How?

eins, zwei, sechs and sieben have different forms when they're used as prefixes. To support these alternate forms, some letters are encoded as decimal digits:

ein5 zw26 sech7 sieb89


Given a digit $$\n\$$ and a parameter $$\m\$$, the correct letter is obtained with:

[ 'enasisn'[(m ^ n) % 10] ]
// 0123456


The outer brackets are used to coerce the result to an empty string if it's undefined.

We use:

• $$\m=0\$$ for a prefix of zehn or zig
• $$\m=2\$$ for a prefix of und
• $$\m=8\$$ for the regular form

This gives the following table:

 m | n | m^n | %10 | letter
---+---+-----+-----+-----------------
0 | 5 |  5  |  5  | 's' -> eins (1)
0 | 2 |  2  |  2  | 'a' \_ zwan (2)
0 | 6 |  6  |  6  | 'n' /
0 | 7 |  7  |  7  |  -  -> sech
0 | 8 |  8  |  8  |  -  \_ sieb
0 | 9 |  9  |  9  |  -  /
---+---+-----+-----+-----------------
2 | 5 |  7  |  7  |  -  -> ein
2 | 2 |  0  |  0  | 'e' \_ zwei
2 | 6 |  4  |  4  | 'i' /
2 | 7 |  5  |  5  | 's' -> sechs
2 | 8 | 10  |  0  | 'e' \_ sieben
2 | 9 | 11  |  1  | 'n' /
---+---+-----+-----+-----------------
8 | 5 | 13  |  3  | 's' -> eins
8 | 2 | 10  |  0  | 'e' \_ zwei
8 | 6 | 14  |  4  | 'i' /
8 | 7 | 15  |  5  | 's' -> sechs
8 | 8 |  0  |  0  | 'e' \_ sieben
8 | 9 |  1  |  1  | 'n' /


(1) Not used
(2) Used for -zig only

# Retina 0.8.2, 162 157 bytes

12
zwo11
11
elf
(\d)(.)
$2und$1zig
3z
3ss
0?und1.+
zehn
0und

0
null
1
eins
su
u
2
zwei
eiz
anz
3
drei
4
vier
5
fuenf
6
sechs
7
sieben
8
acht
9
neun
sz|enz
z


Try it online! Link is to test suite that automatically generates the results for 0-99. Explanation:

12
zwo11
11
elf


Process 12 and 11 first.

(\d)(.)
$2und$1zig


For two digit inputs, switch the digits, insert und, and suffix zig.

3z
3ss


Except dreizig becomes dreissig.

0?und1.+
zehn


nullundeinzig becomes zehn, as does undeinzig when a suffix of any other digit.

0und

0
null


Delete a nullund prefix but a lone 0 becomes null.

1
eins
su
u


1 becomes eins unless it's a prefix in which case it's only ein.

2
zwei
eiz
anz


2 becomes zwei except before zig in which case it becomes zwan.

3
drei
4
vier
5
fuenf
6
sechs
7
sieben
8
acht
9
neun


Translate the remaining digits.

sz|enz
z


Fix up sechszehn, siebenzehn, sechszig and siebenzig to sechzehn, siebzehn, sechzig and siebzig.

Edit: Saved 4 bytes thanks to @Arnauld.

• Processing 11 and 12 before anything else is a bit shorter: 159 bytes Apr 10 at 0:25
• (158 bytes with a minor optimization) Apr 10 at 0:44
• @Arnauld (\d)(.) should be enough, right?
– Neil
Apr 10 at 7:43

# Python + num2words, 54 107 104 91 bytes

lambda n:num2words(n,0,'de').translate({252:'ue',246:'oe',223:'ss'})
from num2words import*


+53 bytes because we have to replace non-ASCII with ASCII equivalents.
-3 thanks to @CreativeName
-13 thanks to @Neil

• You can save three bytes by doing num2words(n,0,"de") instead of num2words(n,lang="de"). Apr 9 at 17:45
• @CreativeName thanks, updated Apr 9 at 17:46
• I think lambda n:num2words(n,0,'de').translate({252:'ue',246:'oe',223:'ss'}) should work.
– Neil
Apr 10 at 0:08
• @Neil thanks, updated Apr 10 at 6:06

# Charcoal, 127 bytes

≔Ｅ⪪”&⌈⪪γ,‴\Π↶QＢσＰc�⁸QＤι,ÀH⁸²V≧τ⁼θＣUH↶⊙↨Ｈ⊖Ｏ³j⪫JＷＸ<⸿Ｄη″β↓”p⪪ιqζＮθ≔﹪θχη¿‹θ¹³⁺⌈§ζθ×s⁼θ¹¿‹θ²⁰⁺⌊§ζη⌈§ζχ«∧η⁺⌈§ζηund⌊§ζ÷θχ⎇⁼³÷θχss¦z¦ig


Try it online! Link is to verbose version of code. Explanation:

≔Ｅ⪪”...”p⪪ιqζ


Split a compressed string on p and then split each substring on q (for the numbers 2, 6 and 7).

Ｎθ≔﹪θχη


Input the number in base 10 and take the remainder modulo 10.

¿‹θ¹³


If the number is less than 13, then...

⁺⌈§ζθ×s⁼θ¹


... output the maximum of the two substrings for that number, plus an extra s if the number is 1.

¿‹θ²⁰


Otherwise, if the number is less than 20, then...

⁺⌊§ζη⌈§ζχ


... output the minimum of the two substrings for that number modulo 10, plus the substring for 10 (although I could have just used the string literal at this point).

«


Otherwise:

∧η⁺⌈§ζηund


If the remainder modulo 10 is not zero, then output the maximum of the two substrings for that number, plus the literal string und.

⌊§ζ÷θχ


Output the minimum of the two substrings for the number integer divided by 10. This outputs zwan for 20-29, sech for 60-69, and sieb for 70-79.

⎇⁼³÷θχss¦z¦ig


Output ssig if the number is in the range 30-39 otherwise output zig.

It looks as if the alternate forms are just the first four letters of the regular form, but this only works if you use the original accented characters, which Charcoal can't compress anyway, and working around the cases of 15 and 50-59 just takes too many bytes.

# Mathematica, 427 bytes

Golfed version, try it online!

Module[{o,t},o={"null","eins","zwei","drei","vier","fuenf","sechs","sieben","acht","neun"};t={"","","zwanzig","dreissig","vierzig","fuenfzig","sechzig","siebzig","achtzig","neunzig"};Which[0<=n<10,o[[n+1]],10<=n<20,StringJoin[{"zehn","elf","zwoelf","dreizehn","vierzehn","fuenfzehn","sechzehn","siebzehn","achtzehn","neunzehn"}[[n-9]]],20<=n<100,StringJoin[{If[Mod[n,10]!=0,o[[Mod[n,10]+1]]<>"und",""],t[[Quotient[n,10]+1]]}]]]


Ungolfed version

germanNumber[n_Integer] := Module[{ones, tens},
ones = {"null", "eins", "zwei", "drei", "vier", "fuenf", "sechs", "sieben", "acht", "neun"};
tens = {"", "", "zwanzig", "dreissig", "vierzig", "fuenfzig", "sechzig", "siebzig", "achtzig", "neunzig"};

Which[
0 <= n < 10, ones[[n + 1]],
10 <= n < 20, StringJoin[{"zehn", "elf", "zwoelf", "dreizehn", "vierzehn", "fuenfzehn", "sechzehn", "siebzehn", "achtzehn", "neunzehn"}[[n - 9]]],
20 <= n < 100, StringJoin[{If[Mod[n, 10] != 0, ones[[Mod[n, 10] + 1]] <> "und", ""], tens[[Quotient[n, 10] + 1]]}]
]
]

• Much more commendable than the "Mathematica always has a builtin approach" (in this case, #~IntegerName~{"German"}&, which would need to be tweaked because it uses umlauts rather than ASCII :) Apr 10 at 15:55

# 05AB1E, 108 bytes

'¡Ö.•вß³á∍>jO5:+VÍÏnтågÏÇ&õL1δç´#₄’WH‹fÈ7Pm•#D9ÝÁè©ć«¦¦®¦¦¦.•8WÏ•š…zig«„iz…iss:.•j(}•3ô'z:®¦ć¨š…und«õšδì)˜Iè


Explanation:

'¡Ö          '# Push dictionary string "null"
.•вß³á∍>jO5:+VÍÏnтågÏÇ&õL1δç´#₄’WH‹fÈ7Pm•
# Push compressed string
#  "eins zwei drei vier fuenf sechs sieben acht neun zehn elf zwoelf"
#           # Split it on spaces to a list
D             # Duplicate this list
9Ý           # Push a list in the range [0,9]
Á          # Rotate it to [9,0,1,2,3,4,5,6,7,8]
è         # 0-based index each in the copy:  ["zehn","eins","zwei","drei","vier",
#  "fuenf","sechs","sieben","acht","neun"]
©        # Store this list in variable ® (without popping)
ć       # Extract head; push first item and remainder-list separately
«      # Append this "zehn" to each item in the remainder-list
¦¦    # Remove the first two items ("einszehn" and "zeizehn")
®             # Push list ® again
¦¦¦          # Remove the first three items ("zehn", "eins", and "zei")
.•8WÏ•š   # Prepend "zwan" to the list
…zig«         # Append "zig" to each item in the list
„iz…iss:     # Replace all "iz" with "iss"
.•j(}•       # Push compressed string "enzsz"
3ô     # Split it into parts of size 3: ["enz","sz"]
'z: '# Replace both with "z"
# (with ®¦¦¦.•8WÏ•š…zig«„iz…iss:.•j(}•3ô'z: we've pushed list
#  ["zwanzig","dreissig","vierzig","fuenfzig","sechzig","siebzig",
#  "achtzig","neunzig"])
®¦            # Push list ®, and remove the first item "zehn"
ć           # Extract head "eins"
¨          # Remove its last letter "s"
š         # Prepend "ein" back to the list
…und«    # Append "und" to each item in the list
õš  # Prepend an empty string "" to the list
δ             # Apply double-vectorized on the two lists:
ì            #  Prepend
)             # Wrap everything on the stack into a list
˜            # Flatten it
Iè          # Use the input to 0-based index into it
# (after which the result is output implicitly)


See this 05AB1E tip of mine (sections How to use the dictionary? and How to compress strings not part of the dictionary?) to understand why '¡Ö is "null"; .•вß³á∍>jO5:+VÍÏnтågÏÇ&õL1δç´#₄’WH‹fÈ7Pm• is "eins zwei drei vier fuenf sechs sieben acht neun zehn elf zwoelf"; .•8WÏ• is "zwan"; and .•j(}• is "enzsz".

# Haskell, 341 bytes

n 0="null"
n 1="eins"
n 10="zehn"
n 11="elf"
n 2="zwei"
n 12="zwoelf"
n 20="zwanzig"
n 3="drei"
n 30="dreissig"
n 4="vier"
n 5="fuenf"
n 6="sechs"
n 16="sechzehn"
n 60="sechzig"
n 7="sieben"
n 17="siebzehn"
n 70="siebzig"
n 8="acht"
n 9="neun"
n t|t<20=n(t-10)++"zehn"
n t|mod t 10==0=n(div t 10)++"zig"
n t=n(mod t 10)++"und"++n(t-mod t 10)


Try it online!