Generate a Random Pronounceable Password

From CodeCodex

Java[edit]

The following NonsenseWords class generates sequences of random syllables following some rules to try to keep them pronounceable.

import java.util.Set;
import java.util.HashSet;
import java.util.Random;
import java.util.EnumSet;

enum Flags
// rules governing choice of consonants:
{
    NotAtEnd, // don't put at end of syllable
    Liquid1, // "l"
    Liquid2, // "r"
    NoLiquid1, // don't follow with a Liquid1
    NoLiquid2, // don't follow with a Liquid2
    NoDoubling, // don't put twice in a row
    NoConsonantBefore, // don't put after another consonant
    NoConsonantAfter, // don't put another consonant after
}

import static Flags.*;

class FlaggedString
// a string with an integer flag mask attached.
{
    protected String theString;
    protected EnumSet<Flags> theFlags;

    public FlaggedString(String aString, EnumSet<Flags> flags)
    {
        theString = aString;
        theFlags = flags;
    } //FlaggedString

    public String getString()
    {
        return theString;
    } //getString

    public EnumSet<Flags> getFlags()
    {
        return theFlags;
    } //getFlags

    public String toString()
    {
        return getString();
    } //toString

} //FlaggedString

public class NonsenseWords
// state-driven syllable generator.
{
    protected static final Random gen = new Random();
// state information used to drive rules:
    protected boolean lastWasVowel = false; // not currently used for any rules
    protected boolean lastWasConsonant = false;
    protected EnumSet<Flags> lastConsonantFlags = EnumSet.noneOf(Flags.class); // only valid if lastWasConsonant
    protected Set<String> forbidden = new HashSet<String>();
    // set of vowels/consonants which can't come after last vowel/consonant

    protected static final String[] vowels =
    {
        "a",
        "ae",
        "ai",
        "ao",
        "au",
        "aw",
        "ay",
        "e",
        "ea",
        "ee",
        "ei",
        "eo",
        "eou",
        "eu",
        "ew",
        "ey",
        "i",
        "ia",
        "ie",
        "io",
        "iou",
        "iu",
        "o",
        "oa",
        "oe",
        "oi",
        "oo",
        "ou",
        "ow",
        "oy",
        "u",
        "ua",
        "ue",
        "uo",
        "uw",
        "uy",
        "y",
    };

    protected static final FlaggedString[] consonants =
    {
        new FlaggedString("b", EnumSet.noneOf(Flags.class)),
        new FlaggedString("ch", EnumSet.of(NoDoubling)),
        new FlaggedString("d", EnumSet.of(NoLiquid1)),
        new FlaggedString("dh", EnumSet.of(NoLiquid1)),
        new FlaggedString("f", EnumSet.noneOf(Flags.class)),
        new FlaggedString("g", EnumSet.noneOf(Flags.class)),
        new FlaggedString("gh", EnumSet.of(NoDoubling)),
        new FlaggedString("h", EnumSet.of(NoLiquid1, NoLiquid2)),
        new FlaggedString("j", EnumSet.of(NoLiquid1, NoLiquid2)),
        new FlaggedString("k", EnumSet.noneOf(Flags.class)),
        new FlaggedString("kh", EnumSet.of(NoDoubling)),
        new FlaggedString("l", EnumSet.of(Liquid1, NoLiquid2)),
        new FlaggedString("m", EnumSet.of(NoLiquid1, NoLiquid2)),
        new FlaggedString("n", EnumSet.of(NoLiquid1, NoLiquid2)),
        new FlaggedString("p", EnumSet.noneOf(Flags.class)),
        new FlaggedString("qu", EnumSet.of(NotAtEnd, NoLiquid1, NoLiquid2)),
        new FlaggedString("r", EnumSet.of(NoLiquid1, Liquid2)),
        new FlaggedString("s", EnumSet.of(NoLiquid2)),
        new FlaggedString("sh", EnumSet.of(NoLiquid2, NoDoubling)),
        new FlaggedString("t", EnumSet.of(NoLiquid1)),
        new FlaggedString("th", EnumSet.of(NoLiquid1, NoDoubling)),
        new FlaggedString("v", EnumSet.of(NoLiquid1, NoLiquid2)),
        new FlaggedString("w", EnumSet.of(NoLiquid1, NoConsonantBefore, NoConsonantAfter)),
        new FlaggedString("wh", EnumSet.of(NotAtEnd, NoLiquid1, NoLiquid2, NoConsonantBefore, NoConsonantAfter)),
        new FlaggedString("x", EnumSet.of(NoLiquid1, NoLiquid2, NoDoubling)),
        new FlaggedString("y", EnumSet.of(NoLiquid1, NoLiquid2, NoDoubling, NoConsonantBefore, NoConsonantAfter)),
        new FlaggedString("z", EnumSet.of(NoLiquid2)),
        new FlaggedString("zh", EnumSet.of(NoDoubling)),
    };

    protected void emptyForbidden()
    // removes all elements from Forbidden.
    {
        forbidden.clear();
    } //emptyForbidden

    public static final int randInt(int max)
    // returns a random integer in the range [0 .. max - 1].
    {
        return gen.nextInt(max);
    } //randInt

    protected String getVowel()
    // returns a random vowel string.
    {
        String result;
        do
        {
            result = vowels[randInt(vowels.length)];
        } while(forbidden.contains(result))
        emptyForbidden();
        forbidden.add(result);
        if (result.length() > 1)
            forbidden.add(result.substring(result.length() - 1));
        lastWasVowel = true;
        lastWasConsonant = false;
        return result;
    } //getVowel

    protected String getConsonant(boolean wantLiquid, boolean atEnd)
    // returns a random consonant string, which might be empty if
    // the previous string was not also a consonant.
    {
        String result;
        while (true)
        {
            int index; // index of consonants entry to return, or negative for null string
            if (wantLiquid)
            {
                if (lastWasConsonant && lastConsonantFlags.contains(NoConsonantAfter))
                    index = -1;
                else
                    index = randInt(consonants.length * 2) - consonants.length;
                    // about 50% odds of returning null string
            }
            else
            {
                if (lastWasConsonant)
                    index = randInt(consonants.length);
                else
                    index = randInt(consonants.length * 2) - consonants.length;
                    // about 50% odds of returning null string
            } //if
            if (index >= 0)
            {
                FlaggedString temp = consonants[index];
                EnumSet<Flags> flags = temp.getFlags();
                if
                  (
                        (
                            !wantLiquid
                        ||
                            (flags.contains(Liquid1) || flags.contains(Liquid2))
                        )
                    &&
                        (
                            !atEnd
                        ||
                            !flags.contains(NotAtEnd)
                        )
                    &&
                        (
                            !lastWasConsonant
                        ||
                                !flags.contains(NoConsonantBefore)
                            &&
                                (
                                    !flags.contains(Liquid1)
                                ||
                                    !lastConsonantFlags.contains(NoLiquid1)
                                )
                            &&
                                (
                                    !flags.contains(Liquid2)
                                ||
                                    !lastConsonantFlags.contains(NoLiquid2)
                                )
                        )
                  )             
                {
                    result = temp.getString();
                    if (!forbidden.contains(result))
                    {
                        lastConsonantFlags = flags;
                        break;
                    } //if
                } //if
            }
            else
            {
                result = "";
                break;
            } //if
        } //while
        if (!result.equals(""))
        {
            emptyForbidden();
            if (lastConsonantFlags.contains(NoDoubling))
                forbidden.add(result);
            lastWasVowel = false;
            lastWasConsonant = true;
        } //if
        return result;
    } //getConsonant

    public String getSyllable()
    // generates and returns a random syllable which can
    // follow the previous one, if any.
    {
        String result;
        result = getConsonant(false, false);
        result += getConsonant(true, false);
        result += getVowel();
        result += getConsonant(true, true);
        result += getConsonant(false, true);
        return result;
    } //getSyllable

    public static String newWord(int minLength, int maxLength)
    // generates a complete random word with a length
    // in [minLength .. maxLength].
    {
        String result;
        do
        {
            NonsenseWords generator = new NonsenseWords();
            result = "";
            while (true)
            {
                result += generator.GetSyllable();
                if (result.length() >= maxLength)
                    break;
                if (result.length() >= minLength)
                {
                    if (generator.randInt(maxLength - result.length()) == 0)
                        break;
                } //if
            } //while
        // Result too long: try again.
        } while (result.length() > maxLength)
        return result;
    } //newWord

} //NonsenseWords

And here is a wrapper mainline that, when invoked from the command line with an integer argument specifying the maximum length of password to generate, will spit out a dozen words following the above rules:

class TestApp
{

    static final int minLength = 4;
    static final int nrWords = 12;

    public static void main(String[] args)
    {
        int maxLength = 12;
        if (Arg.length > 0)
        {
            try
            {
                maxLength = Integer.parseInt(args[0]);
            } //try
            catch (NumberFormatException badNumber)
            {
            // use default value
            } //catch
        } //if
        for (int i = 0; i < nrWords; ++i)
            System.out.println(NonsenseWords.newWord(minLength, maxLength));
    } //main

} //TestApp

Example invocations:

ldo@theon:~> TestApp

weoloipkhey
oooissuwquey
ythquoojchae
oaewhkhoesh
gheoghpoe
tuaoyz
aushmuyeean
deyaemzhaw
oedhqueu
muyeuougoyr
aoyvioukta
geoueuzdhuap

ldo@theon:~> TestApp 24

lloiieppeyzpoefdoantaiio
fiaky
xoaoifkhoowbuylmhoilbtef
foerzopchaoshiuhoitzho
eilxgoadhkeezgio
jeefghuekboa
euslagkhaearuo
ioudchourpquolmhawkhoueu
iethoyoiakhghuas
ghowxioubveourzhbauj
dheyvdhuexewlhshoy
eueouchusaorchruofaewewb