Who has the funniest name?
Who has the funniest name?
During my weekly trivia night (shoutout to our host Ryan), I was<br>enlightened with the knowledge that there once lived a man by the name<br>Preserved<br>Fish. I of course also recalled the famed mathematician Alexander<br>Grothendieck. I endeavoured to find the person with the funniest name. I<br>thought that it was definitely possible to write a program to aid me in<br>this search. Apparently, I am the first person to use a programmatic<br>approach to solve this problem (at least according to Claude), which<br>comes as a shock to me, since the problem seems very tractable (when set<br>up correctly).
All the code is open-source with an MIT license. You can get it here and also find<br>Python notebooks to follow along with the subsequent sections. Note that<br>you want several GB of memory available to load the names dataset.
Part 1: The Proof-of-Concept
Associated Python notebook:<br>part1.ipynb
We begin by defining what it means for a name to sound funny. I<br>considered an approach involving the pronunciation of names, but this<br>seems hard to compute; in addition to computing a pronunciation of a<br>given name, we’d also have to decipher some sort of meaning from that.<br>Consider the classic example name “Hugh Jass” – it’s not immediately<br>obvious to me how to write a program to convert this to the phrase “Huge<br>Ass”. A further consideration is that we’d need to execute this at scale<br>across millions of names1.
In Part 1, we progress by restricting ourselves to names that are<br>composed of real, valid English words (e.g. “Signal Meta”). Depending on<br>how big the result is, we can manually filter down to the funny ones, or<br>use programmatic means to do so.
Given this approach, we now need to source two things: a giant list<br>of names, and a dictionary (in either case, the bigger the better). For<br>our list of names we have a helpful Python package named<br>names-dataset, which contains almost 500 million names<br>scraped from Facebook. This is the most comprehensive one I could find<br>available to the public. For our dictionary, we will use WordNet2.
The biggest issue with a naive implementation is that most<br>dictionaries/word-corpora available do not include all variants of<br>words. For example, the corpus might include the word “jerk” but may not<br>include the past participle “jerked”. Therefore, we will use<br>wordnet.synsets() to handle this for us by accepting all<br>variants of a word.
Finally, common names like Simon are part of many corpora, which is<br>self-defeating for our purposes. It does not make sense to check if a<br>name is one of these words. Therefore, we will exclude proper nouns or<br>“instances” from our dictionary using<br>any(not syn.instance_hypernyms() for syn in wn.synsets(word)).<br>This accepts any word with a non-instance sense, and excludes words<br>where every sense is an instance. For example, Simon would not be<br>considered a valid word since all of its senses/definitions under<br>WordNet are instances (e.g. “Simon the Apostle”), but Faith is<br>acceptable despite being a proper noun since it has a non-instance<br>meaning.
We’ll finish by decorating our is_real_word() function<br>with @lru_cache, which gives us O(1) average time complexity for<br>lookups and updates. Otherwise, we’d have to check the entire dictionary<br>every time we wanted to know if a word is valid, which increases our<br>execution time to an infeasible amount.
Here is our final code:
from names_dataset import NameDataset, NameWrapper<br>from functools import lru_cache<br>import nltk<br>from nltk.corpus import wordnet as wn
nd = NameDataset()<br>full_names = list(zip(nd.first_names.keys(), nd.last_names.keys()))
for corpus in ["wordnet", "omw-1.4"]:<br>try:<br>nltk.data.find(f"corpora/{corpus}")<br>except LookupError:<br>nltk.download(corpus)
@lru_cache(maxsize=None)<br>def is_real_word(word):<br># filter out proper nouns/"instance"s (e.g. "Simon"); only count non-instance senses as real words.<br># wn.synsets() already handles standard inflections ("jerked" -> "jerk").<br>return any(not syn.instance_hypernyms() for syn in wn.synsets(word))
word_names = [name for name in full_names if is_real_word(name[0].lower()) and is_real_word(name[1].lower())]<br>print("number of word names:", len(word_names))<br>print("names:", word_names)<br>The result is saved to names1.txt.
Now we have a solid framework for finding names, and we have produced<br>a working script that has produced a list of names. Unfortunately, the<br>list is only 188 entries long and for the most part contains<br>uninteresting names. We will remedy this in the following parts.
Part 2: Partitioning
Associated Python notebook:<br>part2.ipynb
The main issue with our previous approach is that we are restricting<br>ourselves only to names where the first and last names are each real<br>words. The way I thought of overcoming this is to allow for<br>partitioning, i.e. we will check if parts of names are valid English<br>words as well. To demonstrate what I mean, here is the partitioning<br>code:
from itertools import combinations
MAX_PARTITIONS = 3
def...