# Aircraft registration numbers

Write a program that takes a United States aircraft registration number and returns a 24-bit ICAO hex code corresponding to that registration and vice-versa.

An aircraft registration number always begins with an N and is followed by 1 to 5 characters: 1 to 5 digits and then 0 to 2 uppercase letters. That is, one of the following three patterns:

• N plus 1 to 5 digits
• N plus 1 to 4 digits then one letter
• N plus 1 to 3 digits then two letters

The first digit cannot be 0 and the letters I and O are not used. The letters always follow the digits.

ICAO hex codes assigned to the US are numbers from A00001 to ADF7C7 (in hexadecimal). They are assigned in alphanumeric order (with letters before digits): A00001 corresponds to N1, A00002 corresponds to N1A, A00003 to N1AA, A00004 to N1AB, etc., up to ADF7C7 for N99999. That is, sorted in lexicographic order where A < B < C < D < ... < Y < Z < 0 < 1 < ... < 9.

Here is the order of some codes to help alleviate confusion: N1, N1A, N1AA, N1AB, N1AC, ... , N1AZ, N1B, N1BA, N1BB, ..., N1BZ, N1C, ..., N1Z, N1ZA, N1ZB, ..., N1ZZ, N10.

Alternatively, here's some poorly-written python code that generates the next registration number in order given one as input:

import re

valid=re.compile('N([1-9][0-9]?[0-9]?[A-Z]?[A-Z]?|[1-9][0-9]?[0-9]?[0-9]?[A-Z]?|[1-9][0-9]?[0-9]?[0-9]?[0-9]?)$') ordering='ABCDEFGHJKLMNPQRSTUVWXYZ0123456789' def next_reg(reg,first=True): if first and valid.match(reg+'A') is not None: return reg+'A' last_char=reg[-1] if(last_char=='9'): return next_reg(reg[:-1],False) index=ordering.index(reg[-1]) retval=reg[:-1]+ordering[index+1] if valid.match(retval) is None: return next_reg(reg[:-1],False) return retval  Some test cases: Registration ICAO code (hex) ICAO code (decimal) N17CA A11707 10557191 N1PP A00155 10486101 N959ZP AD5863 11360355 N999ZZ ADF669 11400809 N1000B A0070E 10487566 You can assume no invalid inputs will be given. • Is N1, N1A, N1AA, N1AB, ..., N99999 really just 'alphanumeric order'? I think this part should be clarified. Jan 15 at 16:16 • I think @Arnauld is correct, it seems to me that if it were alphanumeric then N11 should be after N1 and before N1A. – TKoL Jan 15 at 16:22 • @enzo I think it's actually N10, but this really should be explained in the challenge. Jan 15 at 16:55 • @Simd The main purpose of closing an unclear challenge is to protect it from receiving many invalid answers (and potentially downvotes once the answerers realize they didn't understand the task correctly), which is for the OP's own good. Jan 15 at 17:43 • @JonathanAllan Typed the above sentence with two words transposed. D'oh. Typed it correctly in the question if I'm not mistaken. N1A is before N11 because A is less than 1. Fair point about the case, which I've clarified. Jan 15 at 23:42 ## 6 Answers # JavaScript (Node.js), 137 bytes f=(x,s=(i=10384024,'N'))=>g=/\D{3}|.{7}|.\D\d/.test(s)?0:++i&&s==x?i:i^x?[...'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789'].some(t=>f(x,s+t))&&g:s  Try it online! Pass all tests Output 0x9e7299 - 0x9fffff for invalid input N[A-Z]?|N0.*, and false other invalid。 Double-way map • @Arnauld If so it's not much work as I generate pairs – l4m2 Jan 15 at 18:40 • @Arnauld I'm waiting 5min edit cooldown – l4m2 Jan 15 at 18:42 # Perl 5, 136 bytes sub{map{//;map$n{$n{$x="N$'$_"}//=sprintf"A%05X",$i+=!/I|O/}=$x,sort(("",A..Z,AA..ZZ)[0..26*($_<1e3?27:$_<1e4)])}sort 1..99999;$n{+pop}}  Try it online! Generates a list of all 915399 possible N-codes and stores it in hash %n which is also mirrored to convert the other way. • Only seems to work one way, not vice versa as asked in the question? – Neil Jan 16 at 17:41 • @Neil – I missed the and vice-versa. Corrected now. Jan 16 at 18:39 # JavaScript (Node.js), 122 bytes -8 thanks to @l4m2 This is (now even more) similar to l4m2's answer. x=>(n=0x9e7298,g=(s,h=k=>k>52?0:g(s+Buffer([48+k%43]))||h(k+1))=>/I|O|\D{3}|.{7}|.\D\d/.test(s)?0:x^++n?x==s?n:h(17):s)N  Try it online! ### Commented x => ( // x = input n = 0x9e7298, // n = ICAO counter g = ( // g = helper function taking: s, // s = current registration number string h = k => // h = helper function taking a counter k k > 52 ? // if k is greater than 52: 0 // stop : // else: g( // do a recursive call to g s + // where s is completed with either Buffer([ // an upper case letter A-Z (65-90) 48 + k % 43 // or a digit 0-9 (48-57) ]) // ) || // end of recursive call h(k + 1) // if it failed, try h(k + 1) ) => // /I|O|\D{3}|.{7}|.\D\d/ // if s contains a 'I' or a 'O', or has 3 .test(s) ? // consecutive letters, or is 7 char. long, // or contains a digit after a letter (other // than the leading 'N'): 0 // invalid registration number -> abort : // else: x ^ ++n ? // increment n; if x is not equal to n: x == s ? // if x is equal to s: n // return n : // else: h(17) // invoke h, starting with k = 17 for 'A' : // else: s // return s )N // initial call to g with s = 'N'  • 122 – l4m2 Jan 16 at 19:36 # Retina, 173 bytes "G$&\b¶$&|;¶"^~(.+ 99999* L$_
N$.> O 2+%($
;26*
Y\_L
I|O

)L$(;|(\w))(?<=(\w+);.*)$3$2 A.{7} D^ G. .+$&;A,$>:&* 5+;A,(_{16})*(_*) ;A,$#1*_,$.2 TdL,1\d ,B?  No TIO link, since it needs about 9 hours to run. Explanation: "G$&\b¶$&|;¶"^~(  Evaluate the rest of the program below, then select only the line containing the input word and delete the word and separator from that line, thus giving the matching code. .+ 99999* L$_
N$.> O  Generate the numbers from 1 to 99999 in lexicographical order. 2+%( )  Repeat twice for each line. $
;26*
Y\_L
I|O

L$(;|(\w))(?<=(\w+);.*)$3$2  Append each letter except I and O. A.{7} D^ G.  Remove illegal and duplicate entries. .+$&;A,$>:&*  Number the lines with an A prefix. 5+;A,(_{16})*(_*) ;A,$#1*_,\$.2
TdL,1\d
,B?



Convert the numbers to five hexadecimal digits.

# 05AB1E, 51 46 bytes

5°<L§{A„ioмuÐâª˜{õšδ«˜'Nìʒg7‹}ā•¢β¡•+hø.ΔQà}IK


The output is wrapped in a list. This can be fixed at the cost of 1 trailing byte.

Try it online. (Very slow for values far into the sequence.)
The first portion of the program generates all pairs.

Explanation:

5°             # Push 10**5: 100000
<            # Decrease it by 1: 99999
L           # Pop and push a list in the range [1,99999]
§          # Cast each integer to a string
{         # Sort these strings lexographically
A              # Push the lowercase alphabet
„ioм          # Remove the "i" and "o"
u         # Uppercase the remaining letters
Ð        # Triplicate it
â       # Pop two, and create all pairs using the cartesian product
ª      # Convert the remaining string to a list of characters,
# and append this list of pairs
˜     # Flatten it to a single list
{    # Sort these strings lexographically as well
õš  # Prepend an additional empty string
δ              # Apply double-vectorized on the two lists:
«             #  Append two strings together
˜            # Flatten it
'Nì        '# Prepend an "N" before each
ʒ   }    # Filter it, keeping those where:
g       #  Their length
7‹     #  is smaller than 7
#  (to filter out invalid values like "N10000ZZ")
ā              # Push a list in the range [1,length] (without popping)
h       # Then convert each integer to a hexadecimal string
ø              # Pair the values in the two lists together
.Δ             # Pop and find the first/only pair that's truthy for:
à           #  Check if either of the two
Q            #  equals the (implicit) input-string
}IK           # After we've found our matching pair: remove the input from it
# (after which the intended wrapped result is output implicitly)


See this 05AB1E tip of mine (section How to compress large integers?) to understand why •¢β¡• is 10485760.

• This doesn't work right. I think you are including things like N10000ZZ, which isn't valid since it has too many characters. A smaller test case: N1000B should be A0070E, but your program gives A00726. Jan 16 at 15:33
• @Chris You're indeed correct! Should be fixed now, at the cost of 5 bytes. Jan 16 at 20:44

# Charcoal, 151147 121 bytes

≔Ｉ⪪”)¶&*δ§Ｌ¶∧≧Ｔ∨›P”2η≔⪪⁻⁺⁻αI⭆χιO¹υ⊞υω¿›NθNＰθＦ∨¬ⅈ⁵«≔Ｅυ↥⍘⁺↨ＥΦ⁺⪫ＫＡωκν⊕⎇⁼ν⁴⌕υμ⎇№αμ×∨№α§ＫＡν²⁵⌕υμ⁺⁶⁰⁰×Ｉμ§ην¹⌈η¹⁶ζ⎇ⅈ§⮌υ⊕ΣＥζ›κθ⊟ζ


Try it online! Link is to verbose version of code. Explanation: Long because it doesn't use brute force, as that would probably take too long to verify correct program operation, so I've actually computed the result somewhat directly. Explanation:

≔Ｉ⪪”)¶&*δ§Ｌ¶∧≧Ｔ∨›P”2η


Get some compressed magic numbers.

≔⪪⁻⁺⁻αI⭆χιO¹υ⊞υω


Get the string ABCDEFGHJKLMNPQRSTUVWXYZ0123456789, split it into characters, and append the empty string.

¿›NθNＰθ


If this is a US registration, then print it without moving the cursor, otherwise print an N. Note that this means that the x-coordinate is now zero only for a US registration.

Ｆ∨¬ⅈ⁵«


Loop once for a US registration, otherwise 5 times.

≔Ｅυ↥⍘⁺↨ＥΦ⁺⪫ＫＡωκν⊕⎇⁼ν⁴⌕υμ⎇№αμ×∨№α§ＫＡν²⁵⌕υμ⁺⁶⁰⁰×Ｉμ§ην¹⌈η¹⁶ζ


Suffix each letter and digit to the partial US registration and see what the resulting ICAO codes are, plus also calculate the ICAO code for the US registration.

⎇ⅈ§⮌υ⊕ΣＥζ›κθ⊟ζ


For a US registration, overprint it with the ICAO code, but for an ICAO code, append any best letter or digit.

The values of the characters of a US registration are calculated as follows:

• The first digit is worth 101711 times its value plus 601
• The second digit is worth 10111 times its value plus 601
• The third digit is worth 951 times its value plus 601
• The fourth digit is worth 35 times its value plus 601
• The first letter is worth 25 times its position in the alphanumeric string plus 1 unless it's the fifth character
• The fifth character is worth its position in the alphanumeric string plus 1

(In reality 600 is used instead of 601 and then 1` is added later for each digit.)