Alternative to if, else if

别等时光非礼了梦想. 提交于 2019-12-03 05:18:08
Joshua Honig

Why not use everything C# has to offer? The following use of anonymous types, collection initializers, implicitly typed variables, and lambda-syntax LINQ is compact, intuitive, and maintains your modified requirement that patterns be evaluated in order:

var providerMap = new[] {
    new { Pattern = "SWGAS.COM"       , Name = "Southwest Gas" },
    new { Pattern = "georgiapower.com", Name = "Georgia Power" },
    // More specific first
    new { Pattern = "City of Austin"  , Name = "City of Austin" },   
    // Then more general
    new { Pattern = "Austin"          , Name = "Austin Electric Company" }   
    // And for everything else:
    new { Pattern = String.Empty      , Name = "Unknown" }
};

txtVar.Provider = providerMap.First(p => txtVar.BillText.IndexOf(p.Pattern) > -1).Name; 

More likely, the pairs of patterns would come from a configurable source, such as:

var providerMap =
    System.IO.File.ReadLines(@"C:\some\folder\providers.psv")
    .Select(line => line.Split('|'))
    .Select(parts => new { Pattern = parts[0], Name = parts[1] }).ToList();

Finally, as @millimoose points out, anonymous types are less useful when passed between methods. In that case we can define a trival Provider class and use object initializers for nearly identical syntax:

class Provider { 
    public string Pattern { get; set; } 
    public string Name { get; set; } 
}

var providerMap =
    System.IO.File.ReadLines(@"C:\some\folder\providers.psv")
    .Select(line => line.Split('|'))
    .Select(parts => new Provider() { Pattern = parts[0], Name = parts[1] }).ToList();

Since you seem to need to search for the key before returning the value a Dictionary is the right way to go, but you will need to loop over it.

// dictionary to hold mappings
Dictionary<string, string> mapping = new Dictionary<string, string>();
// add your mappings here
// loop over the keys
foreach (KeyValuePair<string, string> item in mapping)
{
    // return value if key found
    if(txtvar.BillText.IndexOf(item.Key) > -1) {
        return item.Value;
    }
}

EDIT: If you wish to have control over the order in which elemnts are evaluated, use an OrderedDictionary and add the elements in the order in which you want them evaluated.

One more using LINQ and Dictionary

var mapping = new Dictionary<string, string>()
                        {
                            { "SWGAS.COM", "Southwest Gas" },
                            { "georgiapower.com", "Georgia Power" }
                            .
                            .
                        };

return mapping.Where(pair => txtvar.BillText.IndexOf(pair.Key) > -1)
              .Select(pair => pair.Value)
              .FirstOrDefault();

If we prefer empty string instead of null when no key matches we can use the ?? operator:

return mapping.Where(pair => txtvar.BillText.IndexOf(pair.Key) > -1)
              .Select(pair => pair.Value)
              .FirstOrDefault() ?? "";

If we should consider the dictionary contains similar strings we add an order by, alphabetically, shortest key will be first, this will pick 'SCE' before 'SCEC'

return mapping.Where(pair => txtvar.BillText.IndexOf(pair.Key) > -1)
              .OrderBy(pair => pair.Key)
              .Select(pair => pair.Value)
              .FirstOrDefault() ?? "";

To avoid the blatant Schlemiel the Painter's approach that looping over all the keys would involve: let's use regular expressions!

// a dictionary that holds which bill text keyword maps to which provider
static Dictionary<string, string> BillTextToProvider = new Dictionary<string, string> {
    {"SWGAS.COM", "Southwest Gas"},
    {"georgiapower.com", "Georgia Power"}
    // ...
};

// a regex that will match any of the keys of this dictionary
// i.e. any of the bill text keywords
static Regex BillTextRegex = new Regex(
    string.Join("|", // to alternate between the keywords
                from key in BillTextToProvider.Keys // grab the keywords
                select Regex.Escape(key))); // escape any special characters in them

/// If any of the bill text keywords is found, return the corresponding provider.
/// Otherwise, return null.
string GetProvider(string billText) 
{
    var match = BillTextRegex.Match(billText);
    if (match.Success) 
        // the Value of the match will be the found substring
        return BillTextToProvider[match.Value];
    else return null;
}

// Your original code now reduces to:

var provider = GetProvider(txtvar.BillText);
// the if is be unnecessary if txtvar.Provider should be null in case it can't be 
// determined
if (provider != null) 
    txtvar.Provider = provider;

Making this case-insensitive is a trivial exercise for the reader.

All that said, this does not even pretend to impose an order on which keywords to look for first - it will find the match that's located earliest in the string. (And then the one that occurs first in the RE.) You do however mention that you're searching through largeish texts; if .NET's RE implementation is at all good this should perform considerably better than 200 naive string searches. (By only making one pass through the string, and maybe a little by merging common prefixes in the compiled RE.)

If ordering is important to you, you might want to consider looking for an implementation of a better string search algorithm than .NET uses. (Like a variant of Boyer-Moore.)

What you want is a Dictionary:

Dictionary<string, string> mapping = new Dictionary<string, string>();
mapping["SWGAS.COM"] = "Southwest Gas";
mapping["foo"] = "bar";
... as many as you need, maybe read from a file ...

Then just:

return mapping[inputString];

Done.

One way of doing it (other answers show very valid options):

void Main()
{
    string input = "georgiapower.com";
    string output = null;

    // an array of string arrays...an array of Tuples would also work, 
    // or a List<T> with any two-member type, etc.
    var search = new []{
        new []{ "SWGAS.COM", "Southwest Gas"},
        new []{ "georgiapower.com", "Georgia Power"},
        new []{ "City of Austin", "City of Austin"}
    };

    for( int i = 0; i < search.Length; i++ ){

        // more complex search logic could go here (e.g. a regex)
        if( input.IndexOf( search[i][0] ) > -1 ){
            output = search[i][1];
            break;
        }
    }

    // (optional) check that a valid result was found.
    if( output == null ){
        throw new InvalidOperationException( "A match was not found." );
    }

    // Assign the result, output it, etc.
    Console.WriteLine( output );
}

The main thing to take out of this exercise is that creating a giant switch or if/else structure is not the best way to do it.

There are several approaches to do this, but for the reason of simplicity, conditional operator may be a choice:

Func<String, bool> contains=x => {
    return txtvar.BillText.IndexOf(x)>-1;
};

txtvar.Provider=
    contains("SWGAS.COM")?"Southwest Gas":
    contains("georgiapower.com")?"Georgia Power":
    contains("City of Austin")?"City of Austin":
    // more statements go here 
    // if none of these matched, txtvar.Provider is assigned to itself
    txtvar.Provider;

Note the result is according to the more preceded condition which is met, so if txtvar.BillText="City of Austin georgiapower.com"; then the result would be "Georgia Power".

you can use dictionary.

Dictionary<string, string> textValue = new Dictionary<string, string>();
foreach (KeyValuePair<string, string> textKey in textValue)
{
  if(txtvar.BillText.IndexOf(textKey.Key) > -1) 
   return textKey.Value;

}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!