Альтернатива if, если if

У меня много аргументов if, else if, и я знаю, что должен быть лучший способ сделать это, но даже после поиска stackoverflow я не уверен, как это сделать в моем конкретном случае.

Я анализирую текстовые файлы (счета) и назначая имя поставщика услуг переменной (txtvar.Provider) на основе того, будут ли отображаться определенные строки на счете.

Это небольшой пример того, что я делаю (не смейтесь, я знаю, что это грязно). В целом, Есть приблизительно 300, если, иначе, если.

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

Я определенно открыт для идей.

РЕДАКТИРОВАТЬ: Чтобы ответить на вопрос, который предполагается... Да, мне понадобилась бы возможность определить порядок, в котором были оценены значения. Как вы можете себе представить, при синтаксическом анализе для сотен немного разных макетов я иногда сталкиваюсь с проблемой отсутствия однозначно уникального индикатора того, к кому принадлежит поставщик услуг. (Спасибо за все замечательные предложения! Я был вне офиса в течение нескольких дней и обойдусь, чтобы попробовать их как можно скорее)

Ответы

Ответ 1

Почему бы не использовать все, что предлагает С#? Следующее использование анонимных типов, инициализаторы коллекции, неявно типизированные переменные и лямбда-синтаксис 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, анонимные типы менее полезны при передаче между методами. В этом случае мы можем определить класс trival 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();

Ответ 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;
    }
}

EDIT: Если вы хотите управлять порядком, в котором оцениваются элементы, используйте OrderedDictionary и добавьте элементы в том порядке, в котором вы хотите их оценить.

Ответ 3

Еще одно использование 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();

Если мы предпочитаем пустую строку вместо нуля, когда нет совпадений ключей, мы можем использовать Оператор:

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

Если мы подумаем, что словарь содержит похожие строки, мы добавим порядок, по алфавиту, самый короткий ключ будет первым, это будет выбирать "SCE" перед "SCEC"

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

Ответ 4

Чтобы избежать вопиющего подхода Шлемиеля, тот подход, который охватывает все ключи: • использовать регулярные выражения!

// 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. (Как вариант Бойер-Мура.)

Ответ 5

То, что вы хотите, это Словарь:

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

Готово.

Ответ 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 не лучший способ сделать это.

Ответ 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;

}