Skip to content

Instantly share code, notes, and snippets.

@Prooffreader
Last active April 19, 2016 10:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Prooffreader/5407b018d7f11b30747f88b8fae228e8 to your computer and use it in GitHub Desktop.
Save Prooffreader/5407b018d7f11b30747f88b8fae228e8 to your computer and use it in GitHub Desktop.
Finding Canadian postal codes that spell words in l33t
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Finding Canadian postal codes that spell words in l33t\n",
"\n",
"By David Taylor, www.prooffreader.com, http://dtdata.io\n",
"\n",
"l33t (leet, 1337) is a simple substitution cipher that started in BBSes in the early 1980s (ah, how I remember my 512 baud modem) that substitutes a few letters for numbers, e.g. '3' for 'E'. My name in l33t (and there were many versions of l33t) might be D4v1d T4y10r (note that it sometimes was not a 1:1 cipher, as in this case both \"i\" and \"l\" were substituted for \"1\").\n",
"\n",
"The simplest versions of l33t for this project were taken from http://www.robertecker.com/hp/research/leet-converter.php.\n",
"\n",
"The list of Canadian postal codes came from http://geocoder.ca/?freedata=1\n",
"\n",
"The list of FSAs (Forward Sorting Areas, the first three letters of a postal code) mapped to the cities in which they appear came from https://www.aggdata.com/free/canada-postal-codes, which seems to be incomplete since 'G3N' appeared in the final list but was not in this file; there may be FSAs that did not appear in postal codes that spelled words that are also missing. The conversion to json was done by me.\n",
"\n",
"The word list is the Moby crossword puzzle list, originally from infochimp, and can be downloaded from this GitHub repo: https://github.com/bgreenlee/scrabble-suggest/tree/master/data/"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Here's a dict of all the possibilities of l33t I used. I don't actually use the dict,\n",
"# since it was just easier to explicitly state the four possible combinations arising from\n",
"# the fact that '6' and '1' can be mapped to two different letters. Unlike in the example\n",
"# above (my name), I did not cases in which the same number repeated in one postal code\n",
"# mapped to two different letters.\n",
"\n",
"all_leets = {'4': ['A'], '3': ['E'], '6': ['G', 'B'], '1': ['I', 'L'], '0': ['O'], '5': ['S'], '7': ['T']}\n",
"leets = []\n",
"leets.append({'4': 'A', '3': 'E', '6': 'G', '1': 'I', '0': 'O', '5': 'S', '7': 'T'})\n",
"leets.append({'4': 'A', '3': 'E', '6': 'G', '1': 'L', '0': 'O', '5': 'S', '7': 'T'})\n",
"leets.append({'4': 'A', '3': 'E', '6': 'B', '1': 'I', '0': 'O', '5': 'S', '7': 'T'})\n",
"leets.append({'4': 'A', '3': 'E', '6': 'B', '1': 'L', '0': 'O', '5': 'S', '7': 'T'})"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['sudser', 'vicars', 'stores', 'houses', 'totals', 'rudely', 'ireful', 'summer', 'scants', 'evaded']\n"
]
}
],
"source": [
"# read the crossword puzzle dictionary, and keep only words that are 6 letters long\n",
"dictionary = set()\n",
"with open('word_list_moby_crossword.flat.txt', 'r') as f:\n",
" for line in f:\n",
" if len(line.strip()) == 6:\n",
" dictionary.add(line.strip().lower())\n",
" \n",
"print(list(dictionary)[:10])"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['A0A1B0', 'A0A1C0', 'A0A1E0', 'A0A1G0', 'A0A1H0', 'A0A1J0', 'A0A1K0', 'A0A1L0', 'A0A1M0', 'A0A1P0']\n"
]
}
],
"source": [
"# load all the postal codes into a list\n",
"postalcodes = []\n",
"with open('postals.txt', 'r') as f:\n",
" for line in f:\n",
" if len(line.strip()) == 6:\n",
" postalcodes.append(line.strip())\n",
"postalcodes.sort()\n",
"\n",
"print(postalcodes[:10])"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"212\n",
"['A1L1N6_ailing', 'B0N1T0_bonito', 'B1A5T5_blasts', 'B1S3C7_bisect', 'B3G1N5_begins', 'B3G1R7_begirt', 'B3H3S7_behest', 'B3L1K3_belike', 'B3L1V3_belive', 'B3M1S7_bemist']\n",
"Wall time: 7.83 s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"# create mapping of postal code to word\n",
"\n",
"# this timing is on a medium-quality computer, an Acer i5 with 16GB RAM running Windows 10\n",
"# (my Linux desktop is in the shop)\n",
"\n",
"pcode_word = set()\n",
"for pcode in postalcodes:\n",
" for leet in leets:\n",
" pcode_ = pcode[:].lower()\n",
" for number, letter in leet.items():\n",
" if number in pcode_ and letter.lower() not in pcode_: # 'A1L0Y5_alloys'\n",
" pcode_ = pcode_.replace(number, letter.lower())\n",
" if pcode_ in dictionary:\n",
" pcode_word.add(pcode.upper()+'_'+pcode_)\n",
"pcode_word = list(pcode_word)\n",
"pcode_word.sort()\n",
"\n",
"print(len(pcode_word))\n",
"print(pcode_word[:10])"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"G3J: Val-Bélair North\n",
"B5A: Yarmouth\n",
"E5A: Moores Mills\n",
"P7K: Thunder Bay West\n"
]
}
],
"source": [
"# import dict of fsa to city\n",
"\n",
"import json\n",
"with open('fsa2city.json', 'r') as f:\n",
" fsa2city = json.load(f)\n",
" \n",
"for i, (fsa, city) in enumerate(fsa2city.items()):\n",
" print('{}: {}'.format(fsa, city))\n",
" if i == 3:\n",
" break"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# make a dict of the two-letter postal abbreviation for provinces and territories\n",
"\n",
"let2prov = {'A': 'NL', 'B': 'NS', 'C': 'PE', 'E': 'NB', 'G': 'QC', \n",
" 'H': 'QC', 'J': 'QC', 'K': 'ON', 'L': 'ON', 'M': 'ON', \n",
" 'N': 'ON', 'P': 'ON', 'R': 'MB', 'S': 'SK', 'T': 'AB', \n",
" 'V': 'BC', 'X': 'NU/NT', 'Y': 'YT'}"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false,
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ailing A1L1N6 Paradise, NL\n",
"bonito B0N1T0 Hants County (Shubenacadie), NS\n",
"blasts B1A5T5 Glace Bay, NS\n",
"bisect B1S3C7 Sydney Central, NS\n",
"begins B3G1N5 Eastern Passage, NS\n",
"begirt B3G1R7 Eastern Passage, NS\n",
"behest B3H3S7 Halifax Lower Harbour, NS\n",
"belike B3L1K3 Halifax Central, NS\n",
"belive B3L1V3 Halifax Central, NS\n",
"bemist B3M1S7 Halifax Bedford Basin, NS\n",
"bemixt B3M1X7 Halifax Bedford Basin, NS\n",
"bemata B3M4T4 Halifax Bedford Basin, NS\n",
"berime B3R1M3 Halifax South, NS\n",
"betime B3T1M3 Lakeside, NS\n",
"betise B3T1S3 Lakeside, NS\n",
"bezils B3Z1L5 Tantallon, NS\n",
"bezels B3Z3L5 Tantallon, NS\n",
"bezant B3Z4N7 Tantallon, NS\n",
"baning B4N1N6 Kentville, NS\n",
"banana B4N4N4 Kentville, NS\n",
"clasts C1A5T5 Charlottetown Southeast Prince Edward Island Provincial Government, PE\n",
"elates E1A7E5 Dieppe Moncton East, NB\n",
"gibers G1B3R5 Beauport North, QC\n",
"givens G1V3N5 Sainte-Foy Northeast, QC\n",
"givers G1V3R5 Sainte-Foy Northeast, QC\n",
"gelate G3L4T3 Saint-Raymond, QC\n",
"genoms G3N0M5 Sainte-Catherine-de-la-Jacques-Cartier, QC\n",
"genips G3N1P5 Sainte-Catherine-de-la-Jacques-Cartier, QC\n",
"garage G4R4G3 Sept-Îles Southeast, QC\n",
"hiking H1K1N6 Anjou East, QC\n",
"hikers H1K3R5 Anjou East, QC\n",
"hiring H1R1N6 Saint-Léonard West, QC\n",
"hirers H1R3R5 Saint-Léonard West, QC\n",
"hiving H1V1N6 Maisonneuve, QC\n",
"hegira H3G1R4 Downtown Montreal Southeast, QC\n",
"hereat H3R3A7 Mount Royal Central, QC\n",
"hexers H3X3R5 Hampstead, QC\n",
"haeing H4E1N6 Ville Émard, QC\n",
"haling H4L1N6 Saint-Laurent Inner Northeast, QC\n",
"halite H4L1T3 Saint-Laurent Inner Northeast, QC\n",
"halers H4L3R5 Saint-Laurent Inner Northeast, QC\n",
"halest H4L3S7 Saint-Laurent Inner Northeast, QC\n",
"halala H4L4L4 Saint-Laurent Inner Northeast, QC\n",
"haring H4R1N6 Saint-Laurent Central, QC\n",
"haslet H4S1E7 Saint-Laurent Southwest, QC\n",
"hawing H4W1N6 Côte-Saint-Luc West, QC\n",
"jebels J3B3L5 Saint-Jean- sur-Richelieu Central, QC\n",
"jalaps J4L4P5 Longueuil Southeast, QC\n",
"japing J4P1N6 Saint-Lambert North, QC\n",
"jawing J4W1N6 Brossard Northwest, QC\n",
"kirtle K1R7L3 Ottawa (West Downtown area), ON\n",
"kiting K1T1N6 Gloucester (Blossom Park / Hunt Club East / Leitrim), ON\n",
"kiters K1T3R5 Gloucester (Blossom Park / Hunt Club East / Leitrim), ON\n",
"kabiki K4B1K1 Cumberland Township, ON\n",
"liaise L1A1S3 Port Hope, ON\n",
"ligans L1G4N5 Oshawa Central, ON\n",
"limina L1M1N4 Whitby North, ON\n",
"liming L1M1N6 Whitby North, ON\n",
"linacs L1N4C5 Whitby Southeast, ON\n",
"lipins L1P1N5 Whitby Southwest, ON\n",
"liters L1T3R5 Ajax Northwest, ON\n",
"living L1V1N6 Pickering Southwest, ON\n",
"livens L1V3N5 Pickering Southwest, ON\n",
"livers L1V3R5 Pickering Southwest, ON\n",
"livest L1V3S7 Pickering Southwest, ON\n",
"lebens L3B3N5 Welland East, ON\n",
"lepers L3P3R5 Markham Central, ON\n",
"levins L3V1N5 Orillia, ON\n",
"levels L3V3L5 Orillia, ON\n",
"levers L3V3R5 Orillia, ON\n",
"levant L3V4N7 Orillia, ON\n",
"lexica L3X1C4 Newmarket Southwest, ON\n",
"lacing L4C1N6 Richmond Hill Southwest, ON\n",
"lacers L4C3R5 Richmond Hill Southwest, ON\n",
"lagers L4G3R5 Aurora, ON\n",
"laking L4K1N6 Concord, ON\n",
"lakers L4K3R5 Concord, ON\n",
"lamina L4M1N4 Barrie North, ON\n",
"laming L4M1N6 Barrie North, ON\n",
"lanose L4N0S3 Barrie South, ON\n",
"lanate L4N4T3 Barrie South, ON\n",
"lapels L4P3L5 Keswick, ON\n",
"larine L4R1N3 Midland, ON\n",
"latens L4T3N5 Mississauga (Malton), ON\n",
"lawine L4W1N3 Mississauga (Matheson / East Rathwood), ON\n",
"lawing L4W1N6 Mississauga (Matheson / East Rathwood), ON\n",
"lazies L4Z1E5 Mississauga (West Rathwood / East Hurontario / SE Gateway), ON\n",
"lazing L4Z1N6 Mississauga (West Rathwood / East Hurontario / SE Gateway), ON\n",
"milers M1L3R5 Scarborough (The Golden Mile / Clairlea / Oakridge / Birchmount Park East), ON\n",
"milage M1L4G3 Scarborough (The Golden Mile / Clairlea / Oakridge / Birchmount Park East), ON\n",
"mimics M1M1C5 Scarborough (Cliffside / Cliffcrest / Scarborough Village West), ON\n",
"miming M1M1N6 Scarborough (Cliffside / Cliffcrest / Scarborough Village West), ON\n",
"minima M1N1M4 Scarborough (Birch Cliff / Cliffside West), ON\n",
"minims M1N1M5 Scarborough (Birch Cliff / Cliffside West), ON\n",
"miners M1N3R5 Scarborough (Birch Cliff / Cliffside West), ON\n",
"miring M1R1N6 Scarborough (Wexford / Maryvale), ON\n",
"miseat M1S3A7 Scarborough (Agincourt), ON\n",
"misact M1S4C7 Scarborough (Agincourt), ON\n",
"macles M4C1E5 East York (Woodbine Heights), ON\n",
"macing M4C1N6 East York (Woodbine Heights), ON\n",
"macers M4C3R5 East York (Woodbine Heights), ON\n",
"macaws M4C4W5 East York (Woodbine Heights), ON\n",
"magics M4G1C5 East York (Leaside), ON\n",
"making M4K1N6 East Toronto (The Danforth West / Riverdale), ON\n",
"makers M4K3R5 East Toronto (The Danforth West / Riverdale), ON\n",
"malice M4L1C3 East Toronto (India Bazaar / The Beaches West), ON\n",
"maline M4L1N3 East Toronto (India Bazaar / The Beaches West), ON\n",
"manics M4N1C5 Central Toronto (Lawrence Park East), ON\n",
"manila M4N1L4 Central Toronto (Lawrence Park East), ON\n",
"marine M4R1N3 Central Toronto (North Toronto West), ON\n",
"marina M4R1N4 Central Toronto (North Toronto West), ON\n",
"mavies M4V1E5 Central Toronto (Summerhill West / Rathnelly / South Hill / Forest Hill SE / Deer Park), ON\n",
"mavins M4V1N5 Central Toronto (Summerhill West / Rathnelly / South Hill / Forest Hill SE / Deer Park), ON\n",
"mawing M4W1N6 Downtown Toronto (Rosedale), ON\n",
"maxima M4X1M4 Downtown Toronto (St. James Town / Cabbagetown), ON\n",
"maxims M4X1M5 Downtown Toronto (St. James Town / Cabbagetown), ON\n",
"nihils N1H1L5 Guelph Northwest, ON\n",
"native N4T1V3 Woodstock North, ON\n",
"pomelo P0M3L0 Algoma, Sudbury District and Greater Sudbury (Chelmsford), ON\n",
"pipits P1P1T5 Gravenhurst, ON\n",
"pecans P3C4N5 Greater Sudbury (Gatchell / West End / Little Britain), ON\n",
"penile P3N1L3 Greater Sudbury (Val Caron), ON\n",
"panics P4N1C5 Timmins Southeast, ON\n",
"paries P4R1E5 Timmins West, ON\n",
"parles P4R1E5 Timmins West, ON\n",
"paring P4R1N6 Timmins West, ON\n",
"psalms P5A1M5 Elliot Lake, ON\n",
"rococo R0C0C0 North Interlake (Stonewall), MB\n",
"realms R3A1M5 Winnipeg (Centennial), MB\n",
"rebops R3B0P5 Winnipeg (Chinatown / Civic Centre / Exchange District), MB\n",
"recons R3C0N5 Winnipeg (Broadway / The Forks / Portage and Main) Manitoba Provincial Government, MB\n",
"regime R3G1M3 Winnipeg (Minto / St. Mathews / Wolseley), MB\n",
"regina R3G1N4 Winnipeg (Minto / St. Mathews / Wolseley), MB\n",
"regive R3G1V3 Winnipeg (Minto / St. Mathews / Wolseley), MB\n",
"reject R3J3C7 Winnipeg (St. James-Assiniboia SE), MB\n",
"relics R3L1C5 Winnipeg (River Heights East), MB\n",
"relict R3L1C7 Winnipeg (River Heights East), MB\n",
"reline R3L1N3 Winnipeg (River Heights East), MB\n",
"relist R3L1S7 Winnipeg (River Heights East), MB\n",
"relive R3L1V3 Winnipeg (River Heights East), MB\n",
"remote R3M0T3 Winnipeg (River Heights Central), MB\n",
"remove R3M0V3 Winnipeg (River Heights Central), MB\n",
"remint R3M1N7 Winnipeg (River Heights Central), MB\n",
"remise R3M1S3 Winnipeg (River Heights Central), MB\n",
"remits R3M1T5 Winnipeg (River Heights Central), MB\n",
"remelt R3M3L7 Winnipeg (River Heights Central), MB\n",
"renigs R3N1G5 Winnipeg (River Heights West), MB\n",
"renins R3N1N5 Winnipeg (River Heights West), MB\n",
"repose R3P0S3 Winnipeg (Fort Garry NW / Tuxedo), MB\n",
"repine R3P1N3 Winnipeg (Fort Garry NW / Tuxedo), MB\n",
"repins R3P1N5 Winnipeg (Fort Garry NW / Tuxedo), MB\n",
"rerose R3R0S3 Winnipeg (Assiniboine South / Betsworth), MB\n",
"rerise R3R1S3 Winnipeg (Assiniboine South / Betsworth), MB\n",
"retime R3T1M3 Winnipeg (Fort Garry NE / University of Manitoba), MB\n",
"retire R3T1R3 Winnipeg (Fort Garry NE / University of Manitoba), MB\n",
"retene R3T3N3 Winnipeg (Fort Garry NE / University of Manitoba), MB\n",
"retake R3T4K3 Winnipeg (Fort Garry NE / University of Manitoba), MB\n",
"revise R3V1S3 Winnipeg (Fort Garry South), MB\n",
"revive R3V1V3 Winnipeg (Fort Garry South), MB\n",
"rewins R3W1N5 Winnipeg (Grassie / Pequis), MB\n",
"rewire R3W1R3 Winnipeg (Grassie / Pequis), MB\n",
"senile S3N1L3 Yorkton, SK\n",
"seniti S3N1T1 Yorkton, SK\n",
"saning S4N1N6 Regina Northeast and East Central, SK\n",
"sanest S4N3S7 Regina Northeast and East Central, SK\n",
"satori S4T0R1 Regina West, SK\n",
"satang S4T4N6 Regina West, SK\n",
"satara S4T4R4 Regina West, SK\n",
"saving S4V1N6 Regina Southeast, SK\n",
"strobe S7R0B3 Saskatoon Northwest, SK\n",
"stroke S7R0K3 Saskatoon Northwest, SK\n",
"striae S7R1A3 Saskatoon Northwest, SK\n",
"tibias T1B1A5 Medicine Hat South, AB\n",
"timing T1M1N6 Coaldale, AB\n",
"tiring T1R1N6 Brooks, AB\n",
"tabors T4B0R5 Airdrie West, AB\n",
"tables T4B1E5 Airdrie West, AB\n",
"tabers T4B3R5 Airdrie West, AB\n",
"tangle T4N6L3 Red Deer Central, AB\n",
"taping T4P1N6 Red Deer North, AB\n",
"tapirs T4P1R5 Red Deer North, AB\n",
"tapeta T4P3T4 Red Deer North, AB\n",
"tarocs T4R0C5 Red Deer South, AB\n",
"taring T4R1N6 Red Deer South, AB\n",
"taxies T4X1E5 Beaumont, AB\n",
"taxing T4X1N6 Beaumont, AB\n",
"taxite T4X1T3 Beaumont, AB\n",
"vibist V1B1S7 Vernon East, BC\n",
"vicars V1C4R5 Cranbrook, BC\n",
"vigils V1G1L5 Dawson Creek, BC\n",
"viking V1K1N6 Merritt, BC\n",
"vimina V1M1N4 Langley Township North, BC\n",
"vining V1N1N6 Castlegar, BC\n",
"vinals V1N4L5 Castlegar, BC\n",
"vitals V1T4L5 Vernon Central, BC\n",
"vixens V1X3N5 Kelowna East Central, BC\n",
"veloce V3L0C3 New Westminster Northeast, BC\n",
"velars V3L4R5 New Westminster Northeast, BC\n",
"venine V3N1N3 Burnaby (East Big Bend / Stride Avenue / Edmonds / Cariboo-Armstrong), BC\n",
"venins V3N1N5 Burnaby (East Big Bend / Stride Avenue / Edmonds / Cariboo-Armstrong), BC\n",
"venire V3N1R3 Burnaby (East Big Bend / Stride Avenue / Edmonds / Cariboo-Armstrong), BC\n",
"verist V3R1S7 Surrey North, BC\n",
"versal V3R5A1 Surrey North, BC\n",
"verste V3R5T3 Surrey North, BC\n",
"vesica V3S1C4 Surrey East, BC\n",
"vestal V3S7A1 Surrey East, BC\n",
"vexils V3X1L5 Surrey Lower West, BC\n",
"vexers V3X3R5 Surrey Lower West, BC\n",
"vacate V4C4T3 Delta Northeast, BC\n",
"vagina V4G1N4 Delta East Central, BC\n",
"vakils V4K1L5 Delta Central, BC\n",
"valise V4L1S3 Delta Southeast, BC\n"
]
}
],
"source": [
"results = []\n",
"for item in pcode_word:\n",
" pcode, word = item.split('_')\n",
" fsa = pcode[:3]\n",
" city = fsa2city[fsa]\n",
" province = let2prov[pcode[0]]\n",
" print('{} {} {}, {}'.format(word, pcode, city, province))\n",
" results.append([word, pcode, city, province])"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.4.4"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment