【问题标题】:Alternative to if, else if替代 if, else if
【发布时间】:2013-09-16 03:46:13
【问题描述】:

我有很多 if,else if 语句,我知道必须有更好的方法来做到这一点,但即使在搜索 stackoverflow 之后,我也不确定如何在我的特定情况下这样做。

我正在解析文本文件(账单)并根据账单上是否出现某些字符串将服务提供商的名称分配给变量 (txtv​​ar.Provider)。

这是我正在做的一个小例子(别笑,我知道这很混乱)。总而言之,大约有 300 个 if,else if。

if (txtvar.BillText.IndexOf("SWGAS.COM") > -1)
{
    txtvar.Provider = "Southwest Gas";
}
else if (txtvar.BillText.IndexOf("georgiapower.com") > -1)
{
    txtvar.Provider = "Georgia Power";
}
else if (txtvar.BillText.IndexOf("City of Austin") > -1)
{
    txtvar.Provider = "City of Austin";
}
// And so forth for many different strings

我想使用类似 switch 语句的东西来提高效率和可读性,但我不确定如何比较 BillText。我正在寻找类似的东西,但不知道如何使它工作。

switch (txtvar.BillText)
{
    case txtvar.BillText.IndexOf("Southwest Gas") > -1:
        txtvar.Provider = "Southwest Gas";
        break;
    case txtvar.BillText.IndexOf("TexasGas.com") > -1:
        txtvar.Provider = "Texas Gas";
        break;
    case txtvar.BillText.IndexOf("Southern") > -1:
        txtvar.Provider = "Southern Power & Gas";
        break;
}

我绝对愿意接受想法。

我需要能够确定评估值的顺序。 您可以想象,在解析数百个略有不同的布局时,我偶尔会遇到一个问题,即没有明确唯一的指标来说明账单属于哪个服务提供商。

【问题讨论】:

  • 对字符串列表的 for 循环?编辑:在BillText, Provider 元组列表上。
  • switch(true) 可以工作。
  • 不需要使用多个标签(尤其是解析等不适用的标签)。此外,没有必要发布数十行冗余代码来说明这个概念。我们在这里很快就明白了。 :-)
  • 您可以使用txtvar.BillText.Contains("value") 来提高可读性
  • 在这里评论,因为它适用于几乎所有的答案。使用字典 (=data) 而不是 if/else (=code) 可以轻松地从外部源(如 csv 文件)读取替换,无需重新编译应用程序即可扩展。

标签: c# performance if-statement dictionary loop-unrolling


【解决方案1】:

为什么不使用 C# 提供的所有功能?以下对匿名类型、集合初始化器、隐式类型变量和 lambda 语法 LINQ 的使用是紧凑、直观的,并且保持了您修改后的要求,即按顺序评估模式:

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; 

更有可能的是,模式对来自可配置的来源,例如:

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();

最后,正如@millimoose 指出的那样,匿名类型在方法之间传递时用处不大。在这种情况下,我们可以定义一个简单的 Provider 类并使用对象初始化器来实现几乎相同的语法:

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();

【讨论】:

  • +1 - 我喜欢这个,虽然它没有考虑不匹配的情况(你可以很容易地使用FirstOrDefault(),然后进行空检查)。
  • 我认为 OP 尚未确认(或否认)检查模式的顺序很重要。
  • +1:另一种好方法,尽管初始化比string[][2] 更多输入。但是,我承认这是一个更通用的解决方案,因为它允许键和值是不同的类型。
  • @TimMedora 如果最后一个Provider 的Pattern 是空字符串,它将保证全面匹配,而不必担心null 结果(不会有@987654330 @属性)
  • @millimoose Aah..我明白你在说什么(指出将匿名类型暴露给其他方法的困难)。在这种情况下,一个简单的 POCO 类以及这里使用的匿名类型就足够了。我只是想在这里展示一个“最好的 C#”展示,用于教学目的。
【解决方案2】:

由于您似乎需要在返回值之前搜索键,所以 Dictionary 是正确的方法,但您需要循环遍历它。

// 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;
    }
}

编辑:如果您希望控制评估元素的顺序,请使用 OrderedDictionary 并按照您希望它们评估的顺序添加元素。

【讨论】:

  • 字典的缺点是您无法控制评估的顺序。您可能会遇到“SCE”转到“Southern California Edison”而“SCEC”转到“South Carolina Electric Company”的情况。您想在 SCE 之前寻找 SCEC。最好改用List&lt;Pair&gt; 结构。您不需要字典哈希提供的快速查找。
  • @MarkLakata OrderedDictionary
  • 没有迹象表明有这种限制。但是 OrderedDictionary 也浮现在我的脑海中,它只是不那么标准,在这种情况下似乎没有必要。但好点,我会添加一个编辑。
  • @MarkLakata 我强烈怀疑这种情况下的订购问题。
  • @MarkLakata 我还认为在这种情况下,最好让 OP 澄清一下,而不是过多地阅读他们选择的控制结构(老实说,else ifs 的 400 行很少见)深思熟虑的选择)并根据假设得出答案。
【解决方案3】:

更多使用 LINQ 和字典

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();

如果在没有键匹配时我们更喜欢空字符串而不是 null,我们可以使用 ??运营商:

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

如果我们应该考虑字典包含类似的字符串,我们添加一个顺序,按字母顺序,最短的键将是第一个,这将在“SCEC”之前选择“SCE”

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

【讨论】:

    【解决方案4】:

    为了避免明显的 Schlemiel the Painter 方法循环遍历所有的键:让我们使用正则表达式!

    // 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;
    

    使这个不区分大小写对读者来说是一个简单的练习。

    话虽如此,这甚至不假装对首先查找的关键字强加一个顺序 - 它会找到位于字符串中最早的匹配项。 (然后是在 RE 中首先出现的那个。)但是您确实提到您正在搜索较大的文本;如果 .NET 的 RE 实现非常好,那么它的性能应该比 200 次简单的字符串搜索要好得多。 (通过只通过字符串一次,并且可能通过合并编译的 RE 中的公共前缀来一点点。)

    如果排序对您很重要,您可能需要考虑寻找一种比 .NET 使用的更好的字符串搜索算法的实现。 (就像 Boyer-Moore 的变体。)

    【讨论】:

    • @TommyGrovnes 现在我觉得自己老了,我从来没有想过“乔尔以前写博客的时候”是“历史”。
    • @millimoose 12 年在这个行业很长一段时间,但时间又是相对的 :)
    【解决方案5】:

    你想要的是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 ...
    

    那么就:

    return mapping[inputString];
    

    完成。

    【讨论】:

    • 正确但并不真正正确。 intputstring 和 key 之间没有等价性。请注意,OP 使用 IndexOf
    • 你没有解决 OP 如何从他们的代码中删除 if else 结构。你所做的只是让txtvar.Provider = "CPS Energy"; 更干净一点,即txtvar.Provider = mapping[searchString];
    【解决方案6】:

    一种方法(其他答案显示非常有效的选项):

    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 );
    }
    

    这个练习的主要内容是创建一个巨大的switch 或if/else 结构并不是最好的方法。

    【讨论】:

    • 不区分大小写的匹配可以用字典来完成。
    • @Asad - 谢谢。从来不知道,但你是对的:stackoverflow.com/questions/6676245/…
    • 您甚至可以在 IEqualityComparer 中实现模糊匹配并将其插入字典。
    • +1 因为它的计算结果与原始 if/else 相同,但更易于阅读。字典方法不做同样的事情。
    • @MarkLakata 虽然我同意字典的传统使用比这种方法更具限制性,但我在回答中使用它的方式在功能上与 Tim 使用数组的方式相同。我们只是以不同的方式存储元素。从逻辑上讲,答案是等价的。
    【解决方案7】:

    有几种方法可以做到这一点,但为了简单起见,条件运算符可能是一种选择:

    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;
    

    请注意,结果是根据满足的更前面的条件,所以如果txtvar.BillText="City of Austin georgiapower.com";,那么结果将是"Georgia Power"。

    【讨论】:

      【解决方案8】:

      你可以使用字典。

      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;
      
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-29
        • 2010-10-14
        • 1970-01-01
        • 1970-01-01
        • 2022-07-27
        • 2019-12-06
        相关资源
        最近更新 更多