Nice post! I also like your blog post about anagrams.
I think there are actually a few ways you could make this a bit quicker. First, for anagram finding, you can do something like this
Select[ToLowerCase@DictionaryLookup[], Sort@Characters@# === Sort@Characters@"elvis" &]
which returns
{"elvis", "evils", "levis", "lives", "veils"}
This is a bit more efficient than generating all of the permutations (as it looked like you were doing in the blog). For blanagrams, there is a somewhat analogous approach.
This gets the same results as your Blanagrams["strings"], but runs in about 1.3 seconds on my computer.
Select[
ToLowerCase@DictionaryLookup[],
StringLength["strings"] === StringLength[#] &&
Total@Select[Merge[{CharacterCounts@"strings", -CharacterCounts@#}, Total], Positive] <= 1 &
]
Let me see if I can explain this because it is a bit cryptic. Most of this code is defining what criteria need to be met for one word to be a blanagram of another. The first criteria is that they have to be the same length. The next criteria is a bit less clear. CharacterCounts gives an association with the counts for each character that appears in the string. The following line of code then finds the differences in the numbers of each character that appears either in # or "strings".
Merge[{CharacterCounts@"strings", -CharacterCounts@#}, Total]
For example, when # is set to be "ratings", this returns
<|"s" -> 1, "g" -> 0, "n" -> 0, "i" -> 0, "r" -> 0, "t" -> 0,
"a" -> -1|>
This says that "strings" and "ratings" have the same number of Gs, Ns, Is, Rs, and Ts, but "strings" has 1 more "s" than "ratings", and "strings" has 1 fewer "a" than "ratings".
We know that the total of this association has to be 0, because the string lengths are the same, and so have the same total number of characters. Therefore, if we just select the positive values, we will get the characters that "strings" contains but # doesn't, but we know that then # will contain an equal number of characters that "strings" doesn't contain. That means that the sum of the positive values of this association will tell us how many substitutions we would need. For your blanagrams, it seems that we want a single substitution or none at all, and so we set that this has to be less than or equal to 1.
I think it would actually be equivalent to take the absolute value of the differences in character counts and then divide by 2, but that seems even more cryptic.
One neat feature of this approach is that we can find double blanagrams (diblanagrams?), where 2 substitutions can be used, and this is no more computationally difficult!
Select[
ToLowerCase@DictionaryLookup[],
StringLength["strings"] === StringLength[#] &&
Total@Select[Merge[{CharacterCounts@"strings", -CharacterCounts@#}, Total], Positive] <= 2 &
]
{"against", "ageists", "agustin", "airguns", "airings", "antsier", \
"arising", "artisan", "artists", "assigns", "austins", "basting", \
"besting", "bestirs", "bigness", "bistros", "bitings", "bossing", \
"brights", "britons", "busting", "carting", "casings", "casting", \
"christs", "cistern", "citrons", "consist", "cosigns", "costing", \
"crating", "cretins", "cringes", "cryings", "cursing", "cussing", \
"darting", "designs", "digests", "digress", "disgust", "dissent", \
"dissing", "dossing", "dotings", "dristan", "dusting", "easting", \
"egoists", "engross", "enlists", "ensigns", "entries", "erasing", \
"fasting", "fessing", "fingers", "firings", "fitness", "frights", \
"fringes", "frisson", "fussing", "gainers", "gaiters", "garnets", \
"garnish", "gassier", "gassing", "gastric", "genesis", "genesis", \
"gingers", "girting", "glisten", "glister", "gnostic", "gnostic", \
"goiters", "goriest", "granite", "grating", "gratins", "gringos", \
"gristle", "gristly", "grotius", "guitars", "gustier", "gusting", \
"gutsier", "hasting", "hinters", "hissing", "histing", "horsing", \
"hosting", "hurting", "ignites", "ignores", "incests", "infests", \
"ingests", "ingrate", "ingress", "insects", "inserts", "insight", \
"insists", "insteps", "insults", "insures", "integer", "interns", \
"inverts", "invests", "issuing", "jesting", "jurists", "kirsten", \
"kissing", "knights", "kristen", "kristin", "lasting", "lingers", \
"listens", "listing", "losings", "lusting", "margins", "marting", \
"martins", "massing", "matings", "messing", "migrant", "minster", \
"minters", "missing", "misters", "misters", "misting", "monists", \
"mossing", "musings", "mussing", "nastier", "negress", "nesting", \
"nighest", "nitrous", "nosiest", "nostril", "nudists", "nursing", \
"nutrias", "orating", "orients", "orients", "origins", "orisons", \
"ousting", "outings", "parings", "parsing", "parting", "passing", \
"pasting", "persist", "pistons", "porting", "posting", "prating", \
"priests", "prisons", "prosing", "purists", "pursing", "racists", \
"rafting", "raising", "raisins", "ranting", "rapists", "rasping", \
"ratings", "rations", "ratting", "ravings", "regains", "regents", \
"regions", "renting", "resents", "resigns", "resists", "resting", \
"retains", "retinas", "retsina", "retying", "reusing", "rifting", \
"ringers", "ringgit", "ringlet", "rinsing", "rioting", "risings", \
"risking", "rooting", "rosiest", "rossini", "rotting", "rousing", \
"routing", "rulings", "rushing", "russian", "rustics", "rusting", \
"rutting", "salting", "sangria", "sargent", "sarnies", "sarongs", \
"sassing", "satires", "savings", "sayings", "scaring", "scoring", \
"scripts", "searing", "seating", "seeings", "seiners", "seniors", \
"seniors", "sensing", "serving", "setting", "sharing", "shiners", \
"shintos", "shoring", "shrines", "shrinks", "sidings", "sifters", \
"sifting", "signals", "signers", "signets", "signora", "signore", \
"signori", "signors", "silents", "silting", "sinatra", "singers", \
"singles", "singlet", "sinkers", "sinners", "sirring", "sisters", \
"sistine", "sitings", "sitters", "sitting", "skating", "skiting", \
"slating", "slights", "smiting", "snaring", "snidest", "snifter", \
"snipers", "snoring", "soaring", "sorties", "sorting", "souring", \
"sousing", "sparing", "spigots", "spinets", "spirits", "spiting", \
"splints", "sporing", "sprains", "sprangs", "spriest", "springs", \
"springy", "sprints", "sprites", "squints", "squirts", "stagier", \
"staging", "stainer", "staking", "staling", "staring", "stating", \
"staving", "staying", "steiner", "stewing", "stigmas", "stiling", \
"stimson", "stinger", "stinker", "stogies", "stoking", "stonier", \
"stoning", "stories", "storing", "stowing", "strains", "straits", \
"strands", "strange", "strides", "strikes", "strings", "stringy", \
"stripes", "striven", "strives", "stygian", "styling", "suiting", \
"suitors", "sunkist", "sunrise", "surfing", "surging", "sussing", \
"sustain", "swinger", "syrians", "syringe", "takings", "tangier", \
"tangier", "tangies", "tarnish", "tarring", "tarting", "tasking", \
"tasting", "taxings", "tearing", "teasing", "tensing", "tensors", \
"terming", "terning", "testing", "thirsts", "throngs", "tidings", \
"tigress", "tilings", "timings", "tingles", "tinkers", "tinsels", \
"tirings", "tocsins", "tonsils", "torsion", "tossing", "touring", \
"tracing", "trading", "tragics", "transit", "treeing", "triking", \
"tristan", "trowing", "truisms", "tryings", "tsarism", "tsarist", \
"tunings", "turfing", "turning", "turnips", "tushing", "twiners", \
"twinges", "unrests", "urgings", "versing", "vesting", "virgins", \
"wasting", "westing", "wingers", "winters", "winters", "wisting", \
"witness", "wrights", "writing", "yessing", "zingers"}