Regex golf and ccTLDs, hooray! Write a regex that only matches valid country code top level domains (ccTLDs). Your regex must match both the domains with the dot and without the dot (it must match tv and .tv). Any domain that is not a ccTLD or does not exist (e.g. .info or .jobs or .xz) must not be matched.

Use Perl, POSIX, PCRE or Python.

For reference, this is the full list of ccTLDs, as of challenge creation:

.ac .ad .ae .af .ag .ai .al .am .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cu .cv .cw .cx .cy .cz .de .dj .dk .dm .do .dz    .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru .rw .sa .sb .sc .sd .se .sg .sh .si .sk .sl .sm .sn .so .sr .ss .st .sv .sx .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .za .zm .zw -- open for registration

.an .bl .bq .bv .eh .gb .mf .sj .su .tp .um -- not open (still must be matched)

^ and $ anchors are not needed.
275 bytes

\.?([cmst][cdghk-orvz]|[agim][delmq-t]|[dft][jkmo]|[acms]x|[bcgkmp][ghmnrwy]|[psu][agksy]|[aen][cegru]|[abcn][fioz]|l[abcikr-vy]|[bdjkprsvy]e|[bcgmnqvz]a|[behpsty]t|[cghmrv]u|[fgksv]i|[gjkmn]p|b[bdjs]|h[kmnr]|[gptw]f|[artz]w|v[cgn]|[joz]m|[ijr]o|[erw]s|[dku]z|[np]l|fr|in|sb)


(277 bytes including ^$.) A Python program was used to autogenerate part of this regular expression. Here is the first version of the program:

def makeregex(list):
  if len(list) == 1:
    return alpha[list[0]]
  regex = "["
  j = list[0]
  while j &lt; 26:
    if j in list:
      if j + 1 in list and j + 2 in list and j + 3 in list:
        regex += alpha[j] + "-"
        while j + 1 in list:
          j += 1
      regex += alpha[j]
    j += 1
  return regex + "]"

def perform(rows, cols):
  replaced = '|'.join([alpha[j] + alpha[k] for j in rows for k in cols if array[j][k] &gt; 0])
  regex = makeregex(rows) + makeregex(cols)
  print regex, 'replaces', replaced, 'saving', len(replaced) - len(regex), 'bytes'
  for j in rows:
    for k in cols:
      if (array[j][k]):
        array[j][k] = -1
      else:
        raise Exception("filling illegal entry")

all = ['ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'ao', 'aq', 'ar', 'as',
       'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
       'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bw', 'by', 'bz', 'ca',
       'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr',
       'cu', 'cv', 'cw', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz',
       'ec', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo',
       'fr', 'ga', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp',
       'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht',
       'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je',
       'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw',
       'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv',
       'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo',
       'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na',
       'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om',
       'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt',
       'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd',
       'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss', 'st',
       'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 'tl',
       'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us',
       'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
       'yt', 'za', 'zm', 'zw']
alpha = 'abcdefghijklmnopqrstuvwxyz'
array = [[alpha[i] + alpha[j] in all for j in range(26)] for i in range(26)]

#perform([2, 12, 18, 19], [2, 3, 6, 7, 10, 11, 12, 13, 14, 17, 21, 25])
#perform([0, 6, 8, 12], [3, 4, 11, 12, 16, 17, 18, 19])
#perform([3, 5, 19], [9, 10, 12, 14])
#perform([0, 2, 12, 18], [23])

while max(max(row) for row in array) &gt; 0:
  best = 0
  i = 1
  while i &lt; 1 &lt;&lt; 26:
    row = [-1 for j in range(26)]
    for j in range(26):
      if (i &amp; 1 &lt;&lt; j):
        for k in range(26):
          if array[j][k] == 0:
            row[k] = 0
          elif array[j][k] == 1 and row[k]:
            row[k] = max(row[k], 0) + 1
        if max(row) &lt; 1:
          break;
    if max(row) &gt; 0:
      total = 3 * sum(max(j, 0) for j in row)
      if total &gt; best:
        rows = [j for j in range(26) if i &amp; 1 &lt;&lt; j]
        cols = [j for j in range(26) if row[j] &gt; 0]
        total -= len(makeregex(rows)) + len(makeregex(cols)) + 1
        if total &gt; best:
          best = total
          bestrows = rows
          bestcols = cols
      i += 1
    else:
      i += i &amp; -i
  if best:
    perform(bestrows, bestcols)
  else:
    print '|'.join([alpha[j] + alpha[k] for j in range(26) for k in range(26) if array[j][k] &gt; 0])
    break


This 282-byte regex results from its output:

\.?([bgmps][aeghmnrsty]|[cmst][cdghk-orvz]|[acn][cfgiloruz]|[agim][delmq-t]|[bck][ghimnrwyz]|[elm][cr-u]|[dft][jkmo]|[agpt][fltw]|[gnv][aegiu]|[lpu][aksy]|[gjkm][emp]|h[kmnrtu]|b[bdfjo]|r[eosuw]|[ls][biv]|[acms]x|[cmz][amw]|[dey]e|dz|eg|fi|fr|in|io|jo|np|om|qa|ug|uz|vc|vn|wf|ws|yt)


The program can also be seeded by adding calls to perform, so I tried seeding it with [cmst][cdghk-orvz]|[agim][delmq-t]|[dft][jkmo]|[acms]x but that just resulted in a rearranged regex. Obviously this wasn't good enough. The problem seemed to be that I was comparing the performance against a simple alternation. I therefore developed a slower but more accurate scoring function, which works by assuming that all of the remaining matches should be either x[yz] or [xy]z alternations, generating the largest alternations first, and compares the result with and without the trial regex.

def makeregex(list):
  if len(list) == 1:
    return alpha[list[0]]
  regex = "["
  j = list[0]
  while j &lt; 26:
    if j in list:
      if j + 1 in list and j + 2 in list and j + 3 in list:
        regex += alpha[j] + "-"
        while j + 1 in list:
          j += 1
      regex += alpha[j]
    j += 1
  return regex + "]"

def simple(array):
  unused = [[j &gt; 0 for j in row] for row in array]
  regex = ""
  while max(max(row) for row in unused):
    rowcount = [sum(row) for row in unused]
    colcount = [sum(col) for col in zip(*unused)]
    if max(colcount) &gt; max(rowcount):
      colindex = colcount.index(max(colcount))
      col = zip(*unused)[colindex]
      regex += "|" + makeregex([j for j in range(26) if col[j]]) + alpha[colindex]
      for j in range(26):
        unused[j][colindex] = 0
    else:
      rowindex = rowcount.index(max(rowcount))
      row = unused[rowindex]
      regex += "|" + alpha[rowindex] + makeregex([j for j in range(26) if row[j]])
      unused[rowindex] = [0 for j in range(26)]
  return regex

def perform(rows, cols):
  previous = simple(array)[1:].split('|')
  for j in rows:
    for k in cols:
      if (array[j][k]):
        array[j][k] = -1
      else:
        raise Exception("filling illegal entry")
  regex = (makeregex(rows) + makeregex(cols) + simple(array)).split('|')
  replaced = '|'.join(j for j in previous if not j in regex)
  regex = '|'.join(j for j in regex if not j in previous)
  print regex, 'replaces', replaced, 'saving', len(replaced) - len(regex), 'bytes'

all = ['ac', 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'ao', 'aq', 'ar', 'as',
       'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh',
       'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bw', 'by', 'bz', 'ca',
       'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'cr',
       'cu', 'cv', 'cw', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz',
       'ec', 'ee', 'eg', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo',
       'fr', 'ga', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gp',
       'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht',
       'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 'it', 'je',
       'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 'kr', 'kw',
       'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 'lu', 'lv',
       'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 'mn', 'mo',
       'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 'mz', 'na',
       'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om',
       'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'ps', 'pt',
       'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd',
       'se', 'sg', 'sh', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'ss', 'st',
       'sv', 'sx', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 'tl',
       'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', 'us',
       'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 'ye',
       'yt', 'za', 'zm', 'zw']
alpha = 'abcdefghijklmnopqrstuvwxyz'
array = [[alpha[i] + alpha[j] in all for j in range(26)] for i in range(26)]

#perform([2, 12, 18, 19], [2, 3, 6, 7, 10, 11, 12, 13, 14, 17, 21, 25])
#perform([0, 6, 8, 12], [3, 4, 11, 12, 16, 17, 18, 19])
#perform([3, 5, 19], [9, 10, 12, 14])
#perform([0, 2, 12, 18], [23])

while max(max(row) for row in array) &gt; 0:
  best = simple(array)[1:]
  bestrows = []
  bestcols = []
  i = 1
  while i &lt; 1 &lt;&lt; 26:
    row = [-1 for j in range(26)]
    for j in range(26):
      if (i &amp; 1 &lt;&lt; j):
        for k in range(26):
          if array[j][k] == 0:
            row[k] = 0
          elif array[j][k] == 1 and row[k]:
            row[k] = max(row[k], 0) + 1
        if max(row) &lt; 1:
          break;
    if max(row) &gt; 0:
      rows = [j for j in range(26) if i &amp; 1 &lt;&lt; j]
      cols = [j for j in range(26) if row[j] &gt; 0]
      unused = [row[:] for row in array]
      for j in rows:
        for k in cols:
          unused[j][k] = 0
      total = makeregex(rows) + makeregex(cols) + simple(unused)
      if len(total) &lt; len(best):
        best = total
        bestrows = rows
        bestcols = cols
      i += 1
    else:
      i += i &amp; -i
  if bestrows:
    perform(bestrows, bestcols)
  else:
    print best
    break


Now without seeding, this actually produces the worse 286-byte regex:

\.?([bgp][ae-hmnrstwy]|[cst][cdghk-orvz]|[nv][acegiu]|m[acdeghk-z]|a[c-gilmoq-uwxz]|k[eghimnprwyz]|l[abcikr-vy]|i[del-oq-t]|s[abeistxy]|c[afiuwxy]|e[cegr-u]|b[bdijoz]|d[ejkmoz]|f[ijkmor]|g[dilpqu]|h[kmnrtu]|n[floprz]|u[agksyz]|r[eosuw]|j[emop]|t[fjtw]|z[amw]|p[kl]|w[fs]|y[et]|om|qa|vn)


When seeded with [cmst][cdghk-orvz] (which I had independently discovered manually before writing the first program) this improves to this 282-byte regex, thus equalling the first program:

\.?([cmst][cdghk-orvz]|[bgmps][aeghmnrsty]|[ag][d-gilmq-uw]|[pt][fkltw]|[acs][cioxz]|[nv][acegiu]|k[eghimnprwyz]|l[abcikr-vy]|i[del-oq-t]|b[bdfijowz]|e[cegr-u]|d[ejkmoz]|f[ijkmor]|h[kmnrtu]|n[floprz]|u[agksyz]|c[afuwy]|m[pquwx]|r[eosuw]|j[emop]|z[amw]|w[fs]|y[et]|gp|om|qa|sb|tj|vn)


After some experimentation I discovered that when seeded with [cmst][cdghk-orvz]|[agim][delmq-t]|[dft][jkmo]|[acms]x (selected from the output of the first version), it results in the given 275-byte regex.

For completeness, I think the best I achieved manually was the following 282-byte regex:

\.?(a[c-gilmoq-uwxz]|b[abd-jostz]|c[afiuwxy]|d[ejkmoz]|e[cegr-u]|f[ijkmor]|g[ad-ilp-u]|h[kmnrtu]|i[del-oq-t]|j[emop]|k[ipz]|l[abcikr-vy]|m[aep-y]|n[acefgilopruz]|om|p[aflst]|qa|r[eosuw]|s[abeistxy]|t[fjtw]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]|[cmst][cdghk-orvz]|[bgkp][eghmnrwy])