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.
FWIW, I know a dude named Thor. And I’ve met a few fellas named Jesus here in Texas.
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.)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.
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.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.?
"""
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.
Try the following when loading the file:The first time I ran it, it choked on the accents. (Or, rather, it produced results like Glór and FÃ\xadlin.)
with open(r'..\Data\us-names-by-gender.csv', encoding = 'utf-8') as name_file:
Fantastic! That did the trick.Try the following when loading the file:
That might helps with the accents.Code:with open(r'..\Data\us-names-by-gender.csv', encoding = 'utf-8') as name_file:
The fun part of this is that since the surnames are patronymic, you've also gone ahead and named their dad too..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}` });

(Dungeons & Dragons)
Rulebook featuring "high magic" options, including a host of new spells.