Skip to content

Instantly share code, notes, and snippets.

@JakobOvrum
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JakobOvrum/515737710619a9d97273 to your computer and use it in GitHub Desktop.
Save JakobOvrum/515737710619a9d97273 to your computer and use it in GitHub Desktop.
Fuzzily convert a string to an enum member
enum FuzzyOptions
{
none,
levenshtein,
caseInsensitive
}
template fuzzyTo(Enum, FuzzyOptions options = FuzzyOptions.levenshtein | FuzzyOptions.caseInsensitive)
if (is(Enum == enum))
{
import std.conv : ConvException;
import std.format : format;
import std.traits : isSomeString;
import std.typecons : tuple;
import std.typetuple : staticMap;
immutable errorFormat = "unable to find member in " ~ Enum.stringof ~ "with name %s";
immutable nameValuePair(string name) = tuple!("name", "value")(name, __traits(getMember, Enum, name));
immutable memberPairs = [staticMap!(nameValuePair, __traits(derivedMembers, Enum))];
Enum fuzzyTo(U)(U input, uint levenshteinThreshold = 3)
if (isSomeString!U && options & FuzzyOptions.levenshtein)
{
import std.algorithm : levenshteinDistance;
static if (options & FuzzyOptions.caseInsensitive)
{
import std.uni : toLowerCase;
auto casedInput = input.toLowerCase;
}
else
alias casedInput = input;
Enum closestValue;
uint smallestDistance = uint.max;
foreach(ref pair; memberPairs)
{
static if (options & FuzzyOptions.caseInsensitive)
auto casedMember = pair.name.toLowerCase;
else
auto casedMember = pair.name;
auto distance = levenshteinDistance(casedInput, casedMember);
if (distance == 0)
return pair.value;
else if (distance < smallestDistance)
{
closestValue = pair.value;
smallestDistance = distance;
}
}
if (smallestDistance <= levenshteinThreshold)
return closestValue;
else
throw new ConvException(format(errorFormat, input));
}
Enum fuzzyTo(U)(U input)
if (isSomeString!U && options == FuzzyOptions.caseInsensitive)
{
import std.uni : icmp;
foreach(ref pair; memberPairs)
{
if (icmp(input, pair.name) == 0)
{
return pair.value;
}
}
throw new ConvException(format(errorFormat, input));
}
Enum fuzzyTo(U)(U input)
if (isSomeString!U && options == FuzzyOptions.none)
{
switch(input)
{
foreach(member; EnumMembers!Enum)
{
case member:
return __traits(getMember, Enum, member);
}
default:
throw new ConvException(format(errorFormat, input));
}
}
}
unittest
{
import std.conv : ConvException;
import std.exception : assertThrown;
enum Test { alpha, beta, gamma }
static immutable fuzzyAlphaNames = ["alpha", "Alphas", "alpa"];
static immutable fuzzyBetaNames = ["beta", "Betas", "bta"];
static immutable fuzzyGammaNames = ["gamma", "Gammas", "gama"];
foreach(fuzzyName; fuzzyAlphaNames)
assert(fuzzyTo!Test(fuzzyName) == Test.alpha);
foreach(fuzzyName; fuzzyBetaNames)
assert(fuzzyTo!Test(fuzzyName) == Test.beta);
foreach(fuzzyName; fuzzyGammaNames)
assert(fuzzyTo!Test(fuzzyName) == Test.gamma);
assertThrown!ConvException(fuzzyTo!Test("asdasdasd"));
static immutable casedAlphaNames = ["Alpha", "ALPHA", "aLpHa"];
static immutable casedBetaNames = ["Beta", "BETA", "bEtA"];
static immutable casedGammaNames = ["Gamma", "GAMMA", "gAmMa"];
foreach(casedName; casedAlphaNames)
assert(fuzzyTo!(Test, FuzzyOptions.caseInsensitive)(casedName) == Test.alpha);
foreach(casedName; casedBetaNames)
assert(fuzzyTo!(Test, FuzzyOptions.caseInsensitive)(casedName) == Test.beta);
foreach(casedName; casedGammaNames)
assert(fuzzyTo!(Test, FuzzyOptions.caseInsensitive)(casedName) == Test.gamma);
assert(fuzzyTo!(Test, FuzzyOptions.levenshtein)("aa") == Test.alpha);
assertThrown!ConvException(fuzzyTo!(Test, FuzzyOptions.levenshtein)("g"));
assert(fuzzyTo!(Test, FuzzyOptions.levenshtein)("g", 4) == Test.beta);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment