nullprogram.com/blog/2009/07/03/
Earlier this year I implemented the
RinkWorks fantasy name generator in Perl. I think lisp lends
itself even better for that, and so I have a partial elisp
implementation for you.
What stands out for me is that the patterns can easily be represented
as a S-expression. We represent substitutions with symbols, literals
with strings, and groups with lists. For example, this pattern,
s(ith|<'C>)V
can be represented in code as,
(s ("ith" ("'" C)) V)
I want a function I can apply to this to generate a name. First, I set
up an association list with symbols and its replacements,
(defvar namegen-subs
'((s ach ack ad age ald ale an ang ar ard as ash at ath augh
aw ban bel bur cer cha che dan dar del den dra dyn
ech eld elm em en end eng enth er ess est et gar gha
hat hin hon ia ight ild im ina ine ing ir is iss it
kal kel kim kin ler lor lye mor mos nal ny nys old om
on or orm os ough per pol qua que rad rak ran ray ril
ris rod roth ryn sam say ser shy skel sul tai tan tas
ther tia tin ton tor tur um und unt urn usk ust ver
ves vor war wor yer)
(v a e i o u y)
...
(d elch idiot ob og ok olph olt omph ong onk oo oob oof oog
ook ooz org ork orm oron ub uck ug ulf ult um umb ump umph
un unb ung unk unph unt uzz))
"Substitutions for the name generator.")
Since we will need this in a couple places, make a function to
randomly select an element from a list,
(defun randth (lst)
"Select random element from the given list."
(nth (random (length lst)) lst))
A function for replacing a symbol,
(defun namegen-select (sym)
"Select a replacement for the given symbol."
(if (null (assoc sym namegen-subs))
(throw 'bad-symbol
(concat "Invalid substitution symbol: " (format "%s" sym)))
(symbol-name (randth (cdr (assoc sym namegen-subs))))))
And finally, the generator. Find a string, pass it through, find a
symbol, substitute it, find a list, pick one element and recurse on
it.
(defun namegen (sexp)
"Generate a name from the given sexp generator."
(cond
((null sexp) "")
((stringp sexp) sexp)
((symbolp sexp) (namegen-select sexp))
((listp sexp)
(concat (if (listp (car sexp)) (namegen (randth (car sexp)))
(namegen (car sexp)))
(namegen (cdr sexp))))))
That's it! We can apply it to the expression above,
(namegen '(s ("ith" ("'" C)) V))
-> "rynithi"
But that's really the easy part. The hard part would be converting the
original pattern into the S-expression, which I don't plan on doing
right now.
Something else to note: this is thousands of times faster than the
Perl version I wrote earlier.
I threw the code in with the rest of my name generation code
(namegen.el),
git clone git://github.com/skeeto/fantasyname.git
S-expressions are handy anywhere.