web-dev-qa-db-ja.com

文字列リストの条件付き初期化

私は、さまざまなソースからのデータを処理できる必要があるプログラムを書いています。ソースはさまざまな形式でデータを出力します。したがって、使用されているソースに応じて、列ヘッダーの異なるセットで文字列リストを初期化する必要があります。ヘッダーの数は、データソースに応じて10〜18列で異なります。

基本的に、リストを条件付きで初期化するクリーンな方法が必要です。私には3つの実用的な解決策がありますが、どちらが最もクリーンでベストな方法であるかはわかりません。

  • 例1では、各ヘッダーをリストに1行ずつ追加する必要があります。それぞれに追加する必要のある潜在的なソースと列の数が多いため、このリストはかなり長くなります。
  • 例2では、​​各caseステートメントを{}で囲んでスコープを制限し、 dataHeaders 各caseステートメントに個別にリストします。範囲を個々のケースステートメントに制限しなければならない状況に遭遇したことは一度もないので、それは悪い習慣だと思います。
  • 例3が最良のオプションのようです。この質問を書いているときに、実際にこれを実装しました。ここには欠点はありませんが、とにかく質問をするつもりだと思いました。

これを行うためのより良い/よりクリーンな方法はありますか?そうでない場合、どの例がベストプラクティスですか?

例#1:

List<string> headers = new List<string>();
switch (dataSource)
{
    case "Source 1":
        headers.Add("header1");
        headers.Add("header2");
        headers.Add("header3");
        headers.Add("...");
        headers.Add("header18");
        break;
    case "Source 2":
        headers.Add("headerA");
        headers.Add("headerB");
        headers.Add("headerC");
        headers.Add("...");
        headers.Add("headerJ");
        break;
    default:
        // TODO: Handle bad sources.
        break;
}

例2:

List<string> headers = new List<string>();
switch (dataSource)
{
    case "Source 1":
        {
            List<string> dataHeaders = new List<string>() { "header1", "header2", "header3", "...", "header18" }
            headers = dataHeaders;
            break;
        }
    case "Source 2":
        {
            List<string> dataHeaders = new List<string>() { "headerA", "headerB", "headerC", "...", "headerJ" }
            headers = dataHeaders;
            break;
        }
    default:
        {
            // TODO: Handle bad sources.
            break;
        }
}

例3:

List<string> headers = new List<string>();
switch (dataSource)
{
    case "Source 1":
        headers.AddRange(new List<string>() { "header1", "header2", "header3", "...", "header18" })
        break;
    case "Source 2":
        headers.AddRange(new List<string>() { "headerA", "headerB", "headerC", "...", "headerJ" })
        break;
    default:
        // TODO: Handle bad sources.
        break;
}
4
TestEngineer

多くの場合、CSV文字列などのプログラミング言語よりも構文が少ない形式でデータを表現することで、このような問題を本当に解決できます。 C#はわかりませんが、Scalaの例は次のとおりです。

def headersForDataSource(dataSource: String): Option[Array[String]] = {
  val headers = """
    Source1,header1,header2,header3,...,header18
    Source2,headerA,headerB,headerC,...,headerJ
    """

  val lines = headers.lines map (_.trim split ',')
  lines find (_(0) == dataSource) map (_.tail)
}

これにより、ヘッダーのリストが大幅にコンパクトになり、読みやすくなります。データ構造の構文が乱雑に混ざらないためです。それは、プログラミング言語によってあなたに強制されたフォーマットの代わりに、それをあなたの好みのフォーマットに入れています。

3
Karl Bielefeldt

また、switchを使用してifおよびmapsを回避することもできます。 Java syntaxisを使用しますが、C#で簡単に実行できると思います

変更不可(静的または定数)記述子

Map<String,String[]> HEADERS_MAP = new HashMap<String,String[]>();
HEADERS_MAP.put("Source 1",new String[]{"header1","headerN"});
HEADERS_MAP.put("Source 2",new String[]{"headerA","headerZ"});
...
//Preventing Map from changes
HEADERS_MAP = Collections.unmodifiableMap(HEADERS_MAP);

注:マップは Dictorinary in C#だと思います

Header builder

//Each consumer will have it's onw list
return Arrays.asList(HEADERS_MAP.get(dataSource));

Edit最初の回答@unholysamplerのコメントで編集しました。

マップはString []を格納し、builderは新しいフレッシュ/非バインドリストを返します。そのため、ビルダーコンシューマーはリストのコンテンツを変更でき、コードが不要な変更を行うのを防ぎます。 Collections.unmodificableListbutそうすれば、コードが少し増えて複雑になります。読みにくいコードを実行します。

1
Laiv

修正しようとしている問題は何ですか?

パフォーマンスの改善を検討している場合、これは間違いなく時期尚早の最適化のケースです。あなたが与える3つの方法のどれでも、ボタンをクリックした後にマウスから指を離すのにかかるほんの一瞬の何千ものアイテムでリストを満たすことができます。

コードの冗長度を低くするだけの場合は、オプション3のバリエーションが最適です。

var headers = new List<string>();
switch (dataSource)
{
    case "Source 1":
        headers.AddRange(new [] { "header1", "header2", "header3", "...", "header18" })
        break;
    case "Source 2":
        headers.AddRange(new [] { "headerA", "headerB", "headerC", "...", "headerJ" })
        break;
    default:
        // TODO: Handle bad sources.
        break;
}

リストのタイプや要素の配列を指定する必要がないことに注意してください-これらはコンパイラによって自動的に推測されます。

1
Peregrine

少し変更したオプション2が最善の策だと思います。ヘッダーの宣言はスイッチの外側に残し、スイッチの内側にheaders = new Listを設定するだけです

また、各caseステートメントのブロックを作成するために中括弧は必要ありません。 C#は、各ケースの最後に強制的にブレークさせます。

これを行う利点は、新しいケースを追加した場合、後でそれを使用しようとするとヘッダーが初期化されないという警告が表示されることです。後で追跡するための論理エラーになります。

0
Andy

これは、ペレグリンとカールビーレフェルトの提案を組み合わせた短いバージョンです。

string hline;
switch (dataSource)
{
    case "Source 1":
        hline = "header1,header2,header3,...,header18";
        break;
    case "Source 2":
        hline = "headerA,headerB,headerC,...,headerZ";
        break;
    default:
        // TODO: Handle bad sources.
        break;
}
var headers = new List<string>();
headers.AddRange(hline.Split(','));

連続する間隔からのインデックスによってさまざまなデータソースを参照できる場合、スイッチケース全体をまったく回避し、次のようにhlineを初期化することを検討します。

 string hLine = new []{
   "header1,header2,header3,...,header18",
   "headerA,headerB,headerC,...,headerZ",
   "..."}[dataSourceIndex];
0
Doc Brown

辞書を使用しないのはなぜですか?

var headers = new List<string>();
var headersToAdd = new Dictionary<string, string[]>
{
    {"Source 1", new string[] {"header1", "header2"} },
    {"Source 2", new string[] {"headerA", "headerB"} }
};

headers.AddRange(headersToAdd["Source 1"]);
headers.AddRange(headersToAdd["Source 2"]);
0
Craig Curtis