15

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

我正在解析文本文件(账单)并根据账单上是否出现某些字符串将服务提供商的名称分配给变量(txtvar.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;
}

我绝对对想法持开放态度。

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

4

8 回答 8

23

为什么不使用 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 指出的那样,匿名类型在方法之间传递时用处不大。在这种情况下,我们可以定义一个 trivalProvider类并使用对象初始化器来实现几乎相同的语法:

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();
于 2013-09-11T23:35:43.563 回答
15

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

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

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

于 2013-09-11T23:10:36.220 回答
10

使用 LINQ 和 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();

如果在没有键匹配时我们更喜欢空字符串而不是 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() ?? "";
于 2013-09-11T23:44:07.627 回答
7

为了避免明显的 Schlemiel,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 的变体。)

于 2013-09-11T23:18:57.787 回答
4

你想要的是一个字典

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

完毕。

于 2013-09-11T23:03:34.250 回答
4

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

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

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

于 2013-09-11T23:07:43.590 回答
1

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

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".

于 2013-09-12T03:22:35.070 回答
0

你可以使用字典。

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;

}
于 2014-01-31T07:00:25.457 回答