[Question]
  [
This is a very very simple algorithm, that I am sure can be solved in many many different languages. In Spain ID cards (known as [*DNI*](https://en.wikipedia.org/wiki/Documento_Nacional_de_Identidad_(Spain))) consist of 8 numbers and a control character. The control character is calculated with the following algorithm: divide the number by 23, take the remainder of the operation and replace it with a character according to this table: 
```
0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22  
T  R  W  A  G  M  Y  F  P  D  X  B  N  J  Z  S  Q  V  H  L  C  K  E
```
If the DNI belongs to a foreign person living in Spain, the first digit is changed to `X`, `Y` or `Z` and it is called an [*NIE*](https://en.wikipedia.org/wiki/NIE_number). In this case, the following substitutions are made before calculating the control character:
```
X Y Z
0 1 2
```
There are a lot of calculators online that help you get the control character, but, how short can you write that code? Write an algorithm (program or function) that receives a `string` with the DNI number (that will always consist of 8 alphanumeric characters) and returns just the single control character calculated and nothing more (a trailing newline is accepted).
Notes:
* The DNI is always written in uppercase, but in your algorithm you can choose the input and output to be upper- or lowercase, just be consistent.
* In real life, some NIEs issued before 2008 have 8 digits after the `X`, `Y` or `Z`, but for the purposes of this game, you can consider they have 7 digits as they have nowadays.
* You can consider that the input string will always have 8 characters, but if they are not in the "8 digits" format nor the "[XYZ] plus 7 digits" format, you must return an error (of your choice) or just throw an exception.
### Test cases:
```
00000010 -> X (HRM Juan Carlos I's DNI number)
01234567 -> L
98765432 -> M
69696969 -> T
42424242 -> Y
Z5555555 -> W (Z=2)
Y0000369 -> S (Y=1)
A1234567 -> <Error code or exception>
1231XX12 -> <Error code or exception>
```
This is [code-golf](/questions/tagged/code-golf "show questions tagged 'code-golf'"), so may the shortest code for each language win!
  
[Answer]
# [Python 3](https://docs.python.org/3/), 83 bytes
```
lambda n:'TRWAGMYFPDXBNJZSQVHLCKE'[int([n,str(ord(n[0])%4)+n[1:]][n[0]in'XYZ'])%23]
```
[Try it online!](https://tio.run/##K6gsycjPM/6fZhvzPycxNyklUSHPSj0kKNzR3TfSLcAlwsnPKyo4MMzDx9nbVT06M69EIzpPp7ikSCO/KEUjL9ogVlPVRFM7L9rQKjY2GsTPzFOPiIxSB4obGcf@LygCaUnTyMwrKC3R0NTU/G9iBIEA "Python 3 – Try It Online")
-5 thanks to [AlixEinsenhardt](https://codegolf.stackexchange.com/users/70489/alix-eisenhardt) (from 99 to 94). -1 thanks to [JonathanAllan](https://codegolf.stackexchange.com/users/53748/jonathan-allan).
[Answer]
# [Haskell](https://www.haskell.org/), 107 93 92 bytes
```
c(x:y)="TRWAGMYFPDXBNJZSQVHLCKE"!!mod(read(("X0Y1Z2"!x):y))23
(a:b:c)!x|x==a=b|2>1=c!x
_!x=x
```
[Try it online!](https://tio.run/##TdHra4MwEADw7/krTr80ATeq9rEK6ei67ln3sqzarhRrw5TVB@o2B/3fXaJj5AKCv0u8eBf6xQc7HOo43X8eGNh@lMB3yHKGUBRnaV7CNE3KPD2czqqAZWWUJggFluWUeZS8w8kYpqGf1wGurB9C1cXLcnJte1dPl@7Fw93KeX69mU/vZ6qi8AI4Z/4eY9XtevrKUJWK8DPEMBH2rZ0VEKU6VpT6dHc0xjoNlAptlYpWdcmKsgDLgjVuy2pNUbJBbYYi4LEGrHab0LuqBh23QxrXhOuG2esPhsLnko/OhoN@zzSE25IPRu0SvpC8Z7RLuCf5qt@G8KXknriO2X7HkXwi3edccs666@qG7BuEYjEV/v@3j4BJ@0Yh9jN7C6IDzaPgm9vBiUMN40KDL8K3Bn4ZhIAz3juuAfCWUZ4hgN@YGOFfgokaThqz/1ETgupf "Haskell – Try It Online")
[Answer]
# Pyth, ~~35~~ 34 bytes
The code contains some unprintable characters, so here is a reversible `xxd` hexdump.
```
00000000: 402e 5043 22fc eeff 1ffc adc7 e614 9451  @.PC"..........Q
00000010: 2247 2573 7358 637a 5d31 3e33 4755 3320  "G%ssXcz]1>3GU3
00000020: 3233                                     23
```
Uses **lowercase** characters.
[Try it online.](http://pyth.herokuapp.com/?code=%40.PC%22%C3%BC%C3%AE%C3%BF%1F%C3%BC%C2%AD%C3%87%C3%A6%14%C2%94Q%22G%25ssXcz%5D1%3E3GU3+23&input=y0000369&debug=0) [Test suite.](http://pyth.herokuapp.com/?code=%40.PC%22%C3%BC%C3%AE%C3%BF%1F%C3%BC%C2%AD%C3%87%C3%A6%14%C2%94Q%22G%25ssXcz%5D1%3E3GU3+23&test_suite=1&test_suite_input=00000010%0A01234567%0A98765432%0A69696969%0A42424242%0Az5555555%0Ay0000369%0Aa1234567%0A1231xx12&debug=0)
### Printable version
```
@.P305777935990456506899534929G%ssXcz]1>3GU3 23
```
### Explanation
* `cz]1` splits the input at position 1, e.g. `"y0000369"` to `["y", "0000369"]`.
* `>3G` gets the last 3 characters of the alphabet, `"xyz"`.
* `U3` gets the range **[0, 3[**, `[0, 1, 2]`.
* `X` maps `xyz` to `[0, 1, 2]` in the split array, e.g. `["y", "0000369"]` to `[1, "0000369"]`. This replaces the first character if it is one of `xyz`, while leaving the tail of 7 characters untouched since any 7 character string can't be equal to a single character.
* `s` joins the array with the empty string, e.g. `[1, "0000369"]` to `"10000369"`.
* `s` casts this string to integer, e.g. `"10000369"` to `10000369`. This throws an error if any extra non-digit characters are left in the string.
* `%`…`23` gets the value modulo 23, e.g. `10000369` to `15`.
* `C"`…`"` converts the binary string from base 256 to integer (about 3.06 × 1026).
* `.P`…`G` gets the permutation of the alphabet with that index.
* `@` gets the correct character from the permutation.
[Answer]
# [MATL](https://github.com/lmendo/MATL), ~~62~~ 59 bytes
```
'RWAGMYFPDXBNJZSQVHLCKET'j'[\dXYZ]\d{7}'XXg'XYZ'I:47+XEU1))
```
The error for not valid input is `A(I): index out of bounds` (compiler running in Octave) or `Index exceeds matrix dimensions` (compiler running in Matlab).
[**Try it online!**](https://tio.run/##y00syfn/Xz0o3NHdN9ItwCXCyc8rKjgwzMPH2ds1RD1LPTomJSIyKjYmpdq8Vj0iIl0dyFP3tDIx145wDTXU1Pz/3wAMDA0A "MATL – Try It Online")
### Explanation
```
'RWAGMYFPDXBNJZSQVHLCKET' % Push this string (output letters circularly shifted by 1)
j                         % Unevaluated input
'[\dXYZ]\d{7}'            % Push this string (regexp pattern)
XX                        % Regexp. Returns cell arary with matching string, or empty
g                         % Convert to standard array. Will be empty if non-valid input
'XYZ'                     % Push this string
I:47+                     % Push [47 48 49] (ASCII codes of '012')
XE                        % Transliterate
U                         % Convert to number
1)                        % Get first entry. Gives an error if empty
)                         % Index (modular, 1-based) into initial string
                          % Implicitly display
```
[Answer]
# ES6, 83 82 81 bytes
```
i=>'TRWAGMYFPDXBNJZSQVHLCKE'[(/^[XYZ]/.test(i)?i.charCodeAt()%4+i.slice(1):i)%23]
```
[In action!](https://jsfiddle.net/2ndAttmt/ngwvz8w0/2/)
Uppercase only, the error code for invalid numbers is `undefined`.
One byte saved thanks to Jonathan Allan.  
Another byte saved thanks to Shaggy.
[Answer]
# Java 8, ~~154~~ ~~145~~ 104 bytes
```
s->{s[0]-=s[0]<88|s[0]>90?0:40;return"TRWAGMYFPDXBNJZSQVHLCKE".charA‌​t(new Integer(new String(s))%23);}
```
-9 bytes thanks to *@OliverGrégoire*.  
-41 bytes thanks to *@OliverGrégoire* again, by taking the input as a char-array (`char[]`).
If the input is invalid, it will either fail with a `java.lang.NumberFormatException` or `java.lang.StringIndexOutOfBoundsException`.
**Explanation:**
[Try it here.](https://tio.run/##zZHZTsJAFIbvfYqTJiZtDE0X9goGEXeIilEWuRiHEYowbaYDQrAJ9/qUvEidUm5p7A1htjPbf@Y7Z0ZohlKOS@io/xngMfI8qCObLo8AbMoJ@0CYQCNcAuAhYoDl0HR74CmW2PVFF83jiNsYGkChBIGXKi@9rtZLlcLxNJ//Dm25oJ1pxbRmMcKnjErPT6@Vq3r78uGidd647TQfX67vq3c1SQ0fqKxXv@vVD5cp@YIbQTIgbDNvcmbTgewpyrFhKpYfWBGBO30fC4ItyMyx@zARgcjRfcGLlCiK5sLjZKI6U6664oiPqUxVLEvapuiapHKnGhIwhhayomzCjJHphpnOZHMJZYV8LptJm0ZCWbYQ1YSytBHVhLJOJioJZe0wkeZOSM4W0U/s9lCJT6oPGHE8lGtzTFxuOxTIXInxKdUYc1gRJDgJb26d/BdGsOitlm4cBEwx/kv2C9M9JJi3PcH4R37wBw) (Invalid test cases are surrounded by try-catch so it doesn't stop at the first error.)
```
s->{                      // Method with char[] parameter and char return-type
  s[0]-=s[0]<88|s[0]>90?  // If the first character is not XYZ:
    0                     //  Leave the first character as is
   :                      // Else:
    40;                   //  Subtract 40 to convert it to 012
  return"TRWAGMYFPDXBNJZSQVHLCKE".charAt(
                          //    Get the char from the String
    new Integer(          //    by converting the following String to an integer:
      new String(s)       //     by converting the char-array to a String
    )%23);                //    And take modulo-23 of that integer
}                         // End of method
```
[Answer]
# [PHP](https://php.net/), 88 bytes
prints 1 for an error
```
$a[0]=strtr(($a=$argn)[0],ZYX,210);echo!ctype_digit($a)?:TRWAGMYFPDXBNJZSQVHLCKE[$a%23];
```
[Try it online!](https://tio.run/##K8go@G9jX5BRwKWSWJSeZ6sUZQoBStb/VRKjDWJti0uKSoo0NFQSbcEqNIFiOlGRETpGhgaa1qnJGfmKySWVBanxKZnpmSVAZZr2ViFB4Y7uvpFuAS4RTn5eUcGBYR4@zt6u0SqJqkbGsdb//wMA "PHP – Try It Online")
[Answer]
# [Jelly](https://github.com/DennisMitchell/jelly), 42 bytes
```
“X0Y1Z2”⁸y@1¦RṪ€V%23ị“Ñ×ⱮEɼiʋ}'uƒẹsø’ṃØA$¤
```
[Try it online!](https://tio.run/##AV0Aov9qZWxsef//4oCcWDBZMVoy4oCd4oG4eUAxwqZS4bmq4oKsViUyM@G7i@KAnMORw5fisa5Fybxpyot9J3XGkuG6uXPDuOKAmeG5g8OYQSTCpP///yI5ODc2NTQzMiI "Jelly – Try It Online")
Too long, Jelly! Dennis is disappointed of you![citation-needed]
[Answer]
# q/kdb+, 68 bytes
**Solution:**
```
{"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}
```
**Examples:**
```
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"00000010"
"X"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"01234567"
"L"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"98765432"
"M"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"69696969"
"T"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"42424242"
"Y"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"Z5555555"
"W"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"Y0000369"
"S"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"A1234567"
" "
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"1231XX12"
" "
```
**Explanation:**
If the first character, `x 0`, is in the string `"XYZ"` then `a` will be `0`, `1` or `2`. If the first character is not in the string, then `a` will be `3`. If `a` is less than 3, we switch out the first character for the string of a (`0`, `1` or `2`), otherwise we switch out for the first character (thus effectively doing nothing).
This string is cast to a long (`"J"$`), which is then `mod`'d with 23 to give the remainder. 
This remainder is used to index into the lookup table.
```
{ "TRWAGMYFPDXBNJZSQVHLCKE" mod["J"$$[3>a:"XYZ"?x 0;string a;x 0],1_x;23] } / ungolfed solution
{                                                                         } / lambda function
                            mod[                                     ;23]   / performds mod 23 of the stuff in the gap
                                                                  1_x       / 1 drop input, drops the first character
                                                                 ,          / concatenation
                                    $[             ;        ;   ]           / if COND then TRUE else FALSE - $[COND;TRUE;FALSE]
                                        a:"XYZ"?x 0                         / "XYZ" find x[0], save result in a
                                      3>                                    / is this result smaller than 3
                                                    string a                / if so, then string a, e.g. 0 -> "0"
                                                             x 0            / if not, just return first character x[0]
                                "J"$                                        / cast to long
  "TRWAGMYFPDXBNJZSQVHLCKE"                                                 / the lookup table
```
**Notes:**
`" "` is returned in the error scenarios, this is because the cast returns a null, and indexing into a string at index null is an empty char. I could add 4 bytes at the beginning (`"!"^`) to make it more obvious that an error had occurred:
```
q){"!"^"TRWAGMYFPDXBNJZSQVHLCKE"("J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x)mod 23}"1231XX12"
"!"
```
[Answer]
# JavaScript(ES6), 121 byte
```
f=i=>{c=+i[0];a=3;while(a--){i[0]=="XYZ"[a]&&(c=a)}b=7;while(b--){c= +i[7-b]+c*10}return "TRWAGMYFPDXBNJZSQVHLCKE"[c%23]}
console.log([f("00000010"),f("01234567"),f("98765432"),f("69696969"),f("42424242"),f("Z5555555"),f("Y0000369"),f("A1234567"),f("1231XX12")])
```
[Answer]
# [Japt](https://github.com/ETHproductions/japt), 50 bytes
Similar to most of the other approaches.
Input and output is lowercase, outputs `undefined` for invalid input.
```
`tr°gmyfpdxbnjzsqvhlcke`g("xyz"øUg)?Uc %4+UÅ:U %23
```
[Test it](http://ethproductions.github.io/japt/?v=1.4.5&code=YHRysGdteWZwZHhibmp6c3F2aGxja2VgZygieHl6IvhVZyk/VWMgJTQrVcU6VSAlMjM=&input=ImExMjM0NTY3Ig==)  
[Test all valid test cases](http://ethproductions.github.io/japt/?v=1.4.5&code=o2B0crBnbXlmcGR4Ym5qenNxdmhsY2tlYGcoInh5eiL4WGcpP1hjICU0K1jFOlggJTIz&input=WyIwMDAwMDAxMCIsIjAxMjM0NTY3IiwiOTg3NjU0MzIiLCI2OTY5Njk2OSIsIjQyNDI0MjQyIiwiejU1NTU1NTUiLCJ5MDAwMDM2OSJd)
[Answer]
# Rust, 206 bytes
I don't think rust is well suited for code golfing -\_-
```
let b=|s:&str|{s.chars().enumerate().map(|(i,c)|match i{0=>match c{'X'=>'0','Y'=>'1','Z'=>'2',_=>c},_=>c}).collect::<String>().parse::<usize>().ok().and_then(|x|"TRWAGMYFPDXBNJZSQVHLCKE".chars().nth(x%23))};
```
[Answer]
# [05AB1E](https://github.com/Adriandmen/05AB1E), ~~41~~ ~~40~~ 39 bytes
```
ć…xyz2ÝJ‡ìDd_i.ǝ}23%.•Xk¦fΣT(:ˆ.Îðv5•sè
```
Takes the input in lowercase (to save 1 byte *yay*)
[Try it online!](https://tio.run/##MzBNTDJM/f//SPujhmUVlVVGh@d6PWpYeHiNS0p8pt7xubVGxqp6jxoWRWQfWpZ2bnGIhtXpNr3DfYc3lJkCRYsPr/j/v9IACIzNLAE "05AB1E – Try It Online")
Prints the input to STDERR if it is malformed
### Explanation
```
ć…xyz2ÝJ‡ìDd_i.ǝ}23%.•Xk¦fΣT(:ˆ.Îðv5•sè
ć                                       # Get head of input and put the rest of the input under it on the stack
 …xyz                                   # Push xyz
     2ÝJ                                # Push 012
        ‡                               # Transliterate
         ì                              # Prepend to the rest of the input
          Dd_                           # Does the result contain something other than numbers?
             i.ǝ}                       # If so print input to STDERR
                 23%                    # Modulo 23
                    .•Xk¦fΣT(:ˆ.Îðv5•   # Pushes the character list
                                     sè # Get the char at the index of the modulo
```
[Answer]
## Dyalog APL, 95 bytes
`{'TRWAGMYFPDXBNJZSQVHLCKE'[1+23|(10⊥¯1+'0123456789'⍳{(⍕{('XYZ'⍳⍵)<4:('XYZ'⍳⍵)-1⋄⍵} ⊃⍵),1↓⍵}⍵)]}`
This is a monadic operator that accepts a character string as its operand and returns its result.
**FIXME** it doesn't check its input. It's not properly golfed.
Usage:
```
    OP ← {'TRWAGMYFPDXBNJZSQVHLCKE'[1+23|(10⊥¯1+'0123456789'⍳{(⍕{('XYZ'⍳⍵)<4:('XYZ'⍳⍵)-1⋄⍵} ⊃⍵),1↓⍵}⍵)]}
      OP '01234567'
L
      OP '00000010'
X
```
]