Last active
August 29, 2015 14:20
-
-
Save JakobOvrum/515737710619a9d97273 to your computer and use it in GitHub Desktop.
Fuzzily convert a string to an enum member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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