Non-evocative Names

MNblockhead

A Title Much Cooler Than Anything on the Old Site
FWIW, I know a dude named Thor. And I’ve met a few fellas named Jesus here in Texas.
1714098877066.png
 

log in or register to remove this ad


Whizbang Dustyboots

Gnometown Hero
Pratchett had some fun Dwarf names including Bashful Bashfulson, Dozy, B'Hrian Bloodaxe and Cheri Sh'rt'azs Littlebottom, it was a great mix of Disney convention, Scandinavian tropes and British humour, and his culture building with the Dwarven grags and gender agnosticism was great social commentary too.
He was decades ahead of the curve on gender-as-a-construct as a mainstream writer. (Not so much in the US, but definitely mainstream in the UK.)
 


ichabod

Legned
Also, I renew my call for some conlang nerds to come up with products for fantasy races, where one could get a big, say, dwarvish dictionary, complete with names and surnames and, as @MNblockhead says, with notations on which names are more common than others. What's the dwarvish version of "John Smith," etc.?
Okay, I whipped together a variant of some code I've written before. It takes a word (name) list and makes new words/names based on three letter frequencies from the original list, counting the start and the end of a word/name. Using some US census data I found online somewhere, I got the following male names from it: Kellyes, Hirontha, Mardolly, Chita, Zarl, Juntabalil, Lenick, Gleshi, Gaddon, Rodiel, Papirver, Kerani, Keriel, Liosz, Kitran, Eranne, Orajohn, Clella.

You could then use the same frequency data to give a "popularity" score to each name. Here's a sample: [(5560, 'Karick'), (5283, 'Alongs'), (4839, 'Tolani'), (4700, 'Herion'), (4695, 'Sirodi'), (4335, 'Porion'), (4204, 'Chels'), (3909, 'Anthael'), (3788, 'Baitn'), (3544, 'Michama'), (3426, 'Saftaro'), (3410, 'Slomana'), (3301, 'Haldena'), (3256, 'Blainte'), (3105, 'Gerleem'), (2357, 'Bhan'), (1117, 'Toicelize'), (721, 'Tyqueredaro')]

Here's some female names for comparison: Kionnasha, Darenee, Khunia, Lalera, Ernet, Tifaela, Gersha, Yerie, Reaun, Kethi, Khylis, Wynniya, Jadahy, Sharal, Raili, Cheryet, Rosara, Caria

Now, these names are from the US census, so they will be biased toward English names, but with various other ethnicities mixed in. But if you could get name lists based on a particular ethnicity, you could generate new names based on that list, and assign those names to a particular fantasy race. Or you could do it with names from a scientific dictionary, or a list of dinosaur names, or a list of names with a 'd' in them.

Code:
"""
nomenclator.py

Yet another fantasy name generator.
"""

import collections
import random

class Nomenclator(object):

    def __init__(self, word_list):
        # Parse the parts of the words.
        self.check = set(word_list)
        self.starts = collections.Counter()
        self.pairs = collections.defaultdict(collections.Counter)
        self.ends = collections.defaultdict(collections.Counter)
        self.lengths = collections.Counter()
        self.mean_length = 0
        for word in word_list:
            word = word.strip()
            self.lengths[len(word)] += 1
            self.mean_length += len(word)
            self.starts[word[0]] += 1
            word = f'^{word}'
            for pair_ndx in range(len(word) - 2):
                self.pairs[word[pair_ndx:(pair_ndx + 2)]][word[pair_ndx + 2]] += 1
            self.ends[word[-3:-1]][word[-1]] += 1
        self.mean_length /= len(word_list)
        # Convert the data to a form usable by choices.
        self.starts = {'population': tuple(self.starts.keys()),
            'weights': tuple(self.starts.values())}
        new_pairs = {}
        for pair in self.pairs:
            new_pairs[pair] = {'population': tuple(self.pairs[pair].keys()),
                'weights': tuple(self.pairs[pair].values())}
        self.pairs = new_pairs
        new_ends = {}
        for almost in self.ends:
            new_ends[almost] = {'population': tuple(self.ends[almost].keys()),
                'weights': tuple(self.ends[almost].values())}
        self.ends = new_ends
        self.lengths = {'population': tuple(self.lengths.keys()),
            'weights': tuple(self.lengths.values())}

    def name(self):
        """Generate a new name. (str)"""
        while True:
            try:
                # Get a random start and length.
                name = '^' + random.choices(**self.starts)[0]
                length = random.choices(**self.lengths)[0]
                # Add letters based on the last two letters of the name.
                while len(name) < length:
                    name += random.choices(**self.pairs[name[-2:]])[0]
                # Add a final character.
                name += random.choices(**self.ends[name[-2:]])[0]
            except KeyError:
                continue
            # Make sure that the name is new.
            name = name[1:]
            if name not in self.check:
                return name

    def names(self, n = 18, popularity = False):
        """
        Generate a list of unique names. (list)

        Parameter:
        n: How many names to generate. (int)
        """
        name_list = []
        while len(name_list) < n:
            new_name = self.name()
            if new_name not in name_list:
                name_list.append(new_name)
        if popularity:
            name_list = [(self.popularity(name), name) for name in name_list]
            name_list.sort(reverse = True)
        return name_list

    def popularity(self, name):
        """
        Get a normalized popularity score for a given name. (int)

        Parameters:
        name: A name that could be generated by this nomenclator. (str)
        """
        len_score = self.lengths['weights'][self.lengths['population'].index(len(name))]
        ltr_score = self.starts['weights'][self.starts['population'].index(name[0])]
        name = f'^{name}'
        for ltr_ndx in range(len(name) - 3):
            ltrs = name[ltr_ndx:(ltr_ndx + 2)]
            weight_ndx = self.pairs[ltrs]['population'].index(name[ltr_ndx + 2])
            ltr_score += self.pairs[ltrs]['weights'][weight_ndx]
        ltrs = name[-3:-1]
        ltr_score += self.ends[ltrs]['weights'][self.ends[ltrs]['population'].index(name[-1])]
        return int((ltr_score / (len(name) - 1)) * self.mean_length + len_score)

if __name__ == '__main__':
    census = []
    with open(r'..\Data\us-names-by-gender.csv') as name_file:
        for line in name_file:
            sex, name, count = line.split(',')
            if sex == 'F':
                census.append(name)
    n = Nomenclator(census)
    print(', '.join(n.names()))
    print(n.names(popularity = True))
 

Okay, I whipped together a variant of some code I've written before.

I've been playing around with this for a bit. Super cool.

Using just male names from the 1880 census gave me some cool ones: Lewton, Wallyn, Tram, Warlie, Morsont, Lawford, Arcusta, Culien, Gustin, Stavis, Ando, Ansonisco, Orreas, Mathams, Iden, Matrando, Theriah, Balmandon.

Then I found a list of Tolkien dwarf names and tried again. (Only 49 names; there were over 900 in the 1880 list.) The first time I ran it, it choked on the accents. (Or, rather, it produced results like Glór and FÃ\xadlin.) So I removed the accents and tried again: Broni, Gimlin, Norin, Forin, Gams, Ibundin, Floin, Froin, Dwali, Irorin, Bombundin, Thali, Bror, Khimli, Grori, Born, Frarvi, Norn.

I will absolutely use this in the future!
 



So I asked ChatGPT to just create a FoundryVTT script macro version of the PowerShell script and it did. And it works.

View attachment 359616

The only thing I didn't like is that it lists common names multiple times in the script so that they are more likely to be rolled. I had ChatGPT rewrite it with weighted values. Which it did. Now, this is a simple script, but by having ChatGPT generate the code I can have a weighted list of hundreds of surnames and hundreds of given names and not have to waste my time copy-pasting or typing them in.

The macro script is below for those interest. I kept the number of names low so the script isn't crazy long.

JavaScript:
let firstNames = {
    "Sigtrygg": 1,
    "Thor": 10,
    "Erik": 10,
    "Harald": 5,
    "Egil": 5,
    "Ragnar": 5,
    "Sven": 5,
    "Bjorn": 5,
    "Alrik": 5,
    "Gudmund": 5,
    "Leif": 5,
    "Vidkun": 5,
    "Sigurd": 5,
    "Ivar": 5,
    "Hrani": 5,
    "Hilding": 5,
    "Olaf": 5,
    "Gunnar": 5,
    "Thorbrand": 5
};

let surnames = {
    "Hrafnsson": 1,
    "Eiriksson": 10,
    "Thorsson": 10,
    "Haraldsson": 5,
    "Egilsson": 5,
    "Ragnarsson": 5,
    "Svennsson": 5,
    "Bjornsson": 5,
    "Alriksson": 5,
    "Gudmundsson": 5,
    "Leifsson": 5,
    "Vidkunsson": 5,
    "Sigurdsson": 5,
    "Ivarsson": 5,
    "Hranisson": 5,
    "Hildingsson": 5,
    "Olafsson": 5,
    "Gunnarsson": 5,
    "Thorbrandsson": 5
};

// Calculate the total weights for first names and surnames
let totalFirstNamesWeight = Object.values(firstNames).reduce((a, b) => a + b, 0);
let totalSurnamesWeight = Object.values(surnames).reduce((a, b) => a + b, 0);

// Generate a random number between 1 and the total weights
let randomFirstNameNumber = Math.floor(Math.random() * totalFirstNamesWeight) + 1;
let randomSurnameNumber = Math.floor(Math.random() * totalSurnamesWeight) + 1;

// Function to find the name based on the random number
function findName(names, randomNumber) {
    let cumulativeWeight = 0;
    for (let name in names) {
        cumulativeWeight += names[name];
        if (randomNumber <= cumulativeWeight) {
            return name;
        }
    }
}

// Retrieve the first name and surname based on the random numbers
let firstName = findName(firstNames, randomFirstNameNumber);
let surname = findName(surnames, randomSurnameNumber);

// Output the generated full name
ChatMessage.create({
    content: `Generated Norse Name: ${firstName} ${surname}`
});
The fun part of this is that since the surnames are patronymic, you've also gone ahead and named their dad too..

Seems like you could potentially generate a whole male lineage this way.
 

Remove ads

Top