Альтернатива if, else 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;
}
Я определенно открыт для идей.
EDIT: чтобы ответить на вопрос, который предполагается... Да, мне понадобится возможность определить порядок, в котором оценивались значения. Как вы можете себе представить, при разборе сотен слегка разных макетов я иногда сталкиваюсь с проблемой отсутствия отчетливо уникального индикатора того, к какому поставщику услуг принадлежит счет. (Спасибо за все удивительные предложения! Я был вне офиса в течение нескольких дней и обойти их как можно скорее)
8 ответов
Почему бы не использовать все, что может предложить C#? Следующее использование анонимных типов, инициализаторов коллекции, неявно типизированных переменных и лямбда-синтаксиса 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 и использовать инициализаторы объектов для почти идентичного синтаксиса:
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();
поскольку вам, похоже, нужно искать ключ, прежде чем возвращать значение a 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
и добавить элементы в том порядке, в котором вы хотите их оценить.
еще один с помощью 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() ?? "";
Если мы должны учитывать, что словарь содержит аналогичные строки, мы добавляем порядок по алфавиту, самый короткий ключ будет первым, это выберет " SCE "перед " SCEC"
return mapping.Where(pair => txtvar.BillText.IndexOf(pair.Key) > -1)
.OrderBy(pair => pair.Key)
.Select(pair => pair.Value)
.FirstOrDefault() ?? "";
чтобы избежать вопиющего подхода Schlemiel художника, который будет включать в себя цикл над всеми ключами: давайте использовать регулярные выражения!
// 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 вообще хороша, это должно выполнять значительно лучше, чем 200 наивных поисков строк. (Сделав только один проход через строку и, возможно, немного, объединив общие префиксы в скомпилированном RE.)
если заказ важен для вас, вы можете рассмотреть возможность поиска реализации лучшего алгоритма поиска строк, чем .NET использует. (Как вариант Бойера-Мура.)
что вы хотите-это словарь:
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];
сделано.
один из способов сделать это (другие ответы показывают очень правильные варианты):
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
структура-не лучший способ сделать это.
есть несколько подходов, чтобы сделать это, но по причине простоты,условный оператор может быть на выбор:
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"
.
вы можете использовать словарь.
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;
}