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) 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. In this case, the following substitutions are made before calculating the control character:

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).


  • 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 , so may the shortest code for each language win!

  • \$\begingroup\$ Sandbox. \$\endgroup\$
    – Charlie
    Jul 3, 2017 at 6:19
  • 2
    \$\begingroup\$ Is it really important that the code have a specific behavior on invalid input? Usually challenges here don't require worrying about error handling. \$\endgroup\$ Jul 3, 2017 at 8:26
  • 3
    \$\begingroup\$ @GregMartin my point precisely, I just wanted the code to show some specific behaviour on error inputs as it is not usually required. \$\endgroup\$
    – Charlie
    Jul 3, 2017 at 8:32
  • \$\begingroup\$ In “divide the number by 23, take the rest of the operation”, the correct term is remainder; rest is too colloquial. \$\endgroup\$
    – Locoluis
    Jul 3, 2017 at 19:14
  • 2
    \$\begingroup\$ @Locoluis in Spanish we say resto, making "rest" a false friend, then. At least I didn't use a wrong term. :-) Thank you! \$\endgroup\$
    – Charlie
    Jul 3, 2017 at 19:19

14 Answers 14


Python 3, 83 bytes

lambda n:'TRWAGMYFPDXBNJZSQVHLCKE'[int([n,str(ord(n[0])%4)+n[1:]][n[0]in'XYZ'])%23]

Try it online!

-5 thanks to AlixEinsenhardt (from 99 to 94). -1 thanks to JonathanAllan.

  • 1
    \$\begingroup\$ You can replace str('XYZ'.index(n[0])) by str(ord(n[0])-88) and save 5 bytes \$\endgroup\$ Jul 3, 2017 at 9:28
  • 1
    \$\begingroup\$ @AlixEisenhardt The above suggestion inspired me to change the technique to a lambda, which eventually saved 10 bytes. \$\endgroup\$
    – Mr. Xcoder
    Jul 3, 2017 at 9:42
  • \$\begingroup\$ Save a byte by replacing -88 with %4. \$\endgroup\$ Jul 3, 2017 at 23:29

Haskell, 107 93 92 bytes


Try it online!

  • \$\begingroup\$ What's the behaviour on invalid inputs? \$\endgroup\$
    – Charlie
    Jul 3, 2017 at 7:08
  • \$\begingroup\$ They will crash the program, I added one in the example. (in practice it throws an exception that nobody catches) \$\endgroup\$
    – bartavelle
    Jul 3, 2017 at 7:12
  • 1
    \$\begingroup\$ I updated the submission with exception catching, so that all tests could be run. \$\endgroup\$
    – bartavelle
    Jul 3, 2017 at 7:17

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. Test suite.

Printable version

@.P305777935990456506899534929G%ssXcz]1>3GU3 23


  • 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).
  • .PG gets the permutation of the alphabet with that index.
  • @ gets the correct character from the permutation.

MATL, 62 59 bytes


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!


'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

ES6, 83 82 81 bytes


In action!

Uppercase only, the error code for invalid numbers is undefined.

One byte saved thanks to Jonathan Allan.
Another byte saved thanks to Shaggy.

  • \$\begingroup\$ Maybe save a byte using %4 rather than -88. \$\endgroup\$ Jul 3, 2017 at 23:32
  • \$\begingroup\$ You should be able to drop the 0 from charCodeAt() too. \$\endgroup\$
    – Shaggy
    Jul 4, 2017 at 9:26

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.


Try it here. (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
                          //    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
  • 1
    \$\begingroup\$ You don't need the | in the regex. Also int t=s.charAt(0)-88 & t<0?t+40:t spare you a byte. \$\endgroup\$ Jul 4, 2017 at 14:09
  • 1
    \$\begingroup\$ Finally, you can return an error code. Just decide that it's 'a' or '0' or any non uppercase letter, and return that instead of t/0 and casting the whole lot to char. You'd save 7 bytes this way, I guess. Golfed this way, you get 145 bytes. \$\endgroup\$ Jul 4, 2017 at 14:20
  • 1
    \$\begingroup\$ @OlivierGrégoire Thanks! I have the feeling it is still possible to use a different way of validating instead of .matches with this regex, btw. But perhaps I'm mistaken. \$\endgroup\$ Jul 4, 2017 at 14:29
  • 1
    \$\begingroup\$ No, you're totally right! It's doable like this: s->{s[0]-=s[0]<88?0:40;return"TRWAGMYFPDXBNJZSQVHLCKE".charAt(new Integer(new String(s))%23);} for only 94 bytes (with s being a char[]) :p \$\endgroup\$ Jul 4, 2017 at 14:35
  • 1
    \$\begingroup\$ Or if you want to be complete about the validation: s[0]<88&s[0]>90 for 8 more bytes. \$\endgroup\$ Jul 4, 2017 at 14:50

PHP, 88 bytes

prints 1 for an error


Try it online!


Jelly, 42 bytes


Try it online!

Too long, Jelly! Dennis is disappointed of you![citation-needed]


q/kdb+, 68 bytes


{"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}


q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"00000010"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"01234567"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"98765432"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"69696969"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"42424242"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"Z5555555"
q){"TRWAGMYFPDXBNJZSQVHLCKE"mod["J"$$[3>a:"XYZ"?x 0;($)a;x 0],1_x;23]}"Y0000369"
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"
" "


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


" " 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"

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]}



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
Test all valid test cases


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))};

05AB1E, 41 40 39 bytes


Takes the input in lowercase (to save 1 byte yay)

Try it online!

Prints the input to STDERR if it is malformed


ć                                       # 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

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.


    OP ← {'TRWAGMYFPDXBNJZSQVHLCKE'[1+23|(10⊥¯1+'0123456789'⍳{(⍕{('XYZ'⍳⍵)<4:('XYZ'⍳⍵)-1⋄⍵} ⊃⍵),1↓⍵}⍵)]}

      OP '01234567'

      OP '00000010'

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.