私はif、else ifステートメントをたくさん持っており、これを行うためのより良い方法が必要であることを知っていますが、stackoverflowを検索した後でも、特定の場合にそれを行う方法がわかりません。
テキストファイル(請求書)を解析し、特定の文字列が請求書に表示されているかどうかに基づいて、サービスプロバイダーの名前を変数(txtvar.Provider)に割り当てています。
これは私がしていることの小さなサンプルです(笑わないでください、私はそれが厄介であることを知っています)。全体として、ifが約300あり、それ以外の場合は約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;
}
私は間違いなくアイデアを受け入れています。
値が評価された順序を決定する機能が必要になります。ご想像のとおり、わずかに異なる数百のレイアウトを解析するときに、請求書がどのサービスプロバイダーに属しているかについて明確に一意のインジケーターがないという問題に遭遇することがあります。
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が指摘しているように、匿名型はメソッド間で渡される場合はあまり役に立ちません。その場合、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();
値を返す前にキーを検索する必要があるように思われるので、 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と辞書を使用してもう1つ
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() ?? "";
すべてのキーをループするという画家の露骨なシュレミエルのアプローチを避けるために、正規表現を使用しましょう!
// 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の単純な文字列検索よりもかなり優れたパフォーマンスを発揮するはずです。 (文字列を1回通過するだけで、コンパイルされたREの共通プレフィックスをマージすることで少しだけです。)
順序付けが重要な場合は、.NETが使用するよりも優れた文字列検索アルゴリズムの実装を探すことを検討してください。 (ボイヤームーアの変種のように。)
それを行う1つの方法(他の回答は非常に有効なオプションを示しています):
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
構造を作成することはそれを行うための最良の方法ではないということです。
あなたが欲しいのは 辞書 :
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];
完了。
これを行うにはいくつかのアプローチがありますが、簡単にするために、条件演算子を選択できます。
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;
}