リストまたは配列を使用する必要がありますか?


22

アイテム番号のUPCを計算するために、Windowsフォームで作業しています。

一度に1つのアイテム番号/ UPCを処理するものを正常に作成しました。次に、複数のアイテム番号/ UPCに対して拡張および実行したいと思います。

リストを使い始めてみましたが、行き詰まってしまいます。ヘルパークラスを作成しました。

public class Codes
{
    private string incrementedNumber;
    private string checkDigit;
    private string wholeNumber;
    private string wholeCodeNumber;
    private string itemNumber;

    public Codes(string itemNumber, string incrementedNumber, string checkDigit, string wholeNumber, string wholeCodeNumber)
    {
        this.incrementedNumber = incrementedNumber;
        this.checkDigit = checkDigit;
        this.wholeNumber = wholeNumber;
        this.wholeCodeNumber = wholeCodeNumber;
        this.itemNumber = itemNumber;
    }

    public string ItemNumber
    {
        get { return itemNumber; }
        set { itemNumber = value; }
    }

    public string IncrementedNumber
    { 
        get { return incrementedNumber; }
        set { incrementedNumber = value; } 
    }

    public string CheckDigit
    {
        get { return checkDigit; }
        set { checkDigit = value; }
    }

    public string WholeNumber
    {
        get { return wholeNumber; }
        set { wholeNumber = value; }
    }

    public string WholeCodeNumber
    {
        get { return wholeCodeNumber; }
        set { wholeCodeNumber = value; }
    }

}

その後、コードを開始しましたが、問題はプロセスがインクリメンタルであるということです。つまり、グリッドビューからチェックボックスを介してアイテム番号を取得し、リストに追加します。次に、データベースから最後のUPCを取得し、チェックデジットを削除してから、1ずつ増やしてリストに追加します。次に、新しい番号のチェックデジットを計算して、リストに入れます。そして、ここで私はすでにメモリ不足の例外を取得しています。ここに私がこれまでに持っているコードがあります:

List<Codes> ItemNumberList = new List<Codes>();


    private void buttonSearch2_Click(object sender, EventArgs e)
    {
        //Fill the datasets
        this.immasterTableAdapter.FillByWildcard(this.alereDataSet.immaster, (textBox5.Text));
        this.upccodeTableAdapter.FillByWildcard(this.hangtagDataSet.upccode, (textBox5.Text));
        this.uPCTableAdapter.Fill(this.uPCDataSet.UPC);
        string searchFor = textBox5.Text;
        int results = 0;
        DataRow[] returnedRows;
        returnedRows = uPCDataSet.Tables["UPC"].Select("ItemNumber = '" + searchFor + "2'");
        results = returnedRows.Length;
        if (results > 0)
        {
            MessageBox.Show("This item number already exists!");
            textBox5.Clear();
            //clearGrids();
        }
        else
        {
            //textBox4.Text = dataGridView1.Rows[0].Cells[1].Value.ToString();
            MessageBox.Show("Item number is unique.");
        }
    }

    public void checkMarks()
    {

        for (int i = 0; i < dataGridView7.Rows.Count; i++)
        {
            if ((bool)dataGridView7.Rows[i].Cells[3].FormattedValue)
            {
                {
                    ItemNumberList.Add(new Codes(dataGridView7.Rows[i].Cells[0].Value.ToString(), "", "", "", ""));
                }
            }
        }
    }

    public void multiValue1()
    {
        _value = uPCDataSet.UPC.Rows[uPCDataSet.UPC.Rows.Count - 1]["UPCNumber"].ToString();//get last UPC from database
        _UPCNumber = _value.Substring(0, 11);//strip out the check-digit
        _UPCNumberInc = Convert.ToInt64(_UPCNumber);//convert the value to a number

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            _UPCNumberInc = _UPCNumberInc + 1;
            _UPCNumberIncrement = Convert.ToString(_UPCNumberInc);//assign the incremented value to a new variable
            ItemNumberList.Add(new Codes("", _UPCNumberIncrement, "", "", ""));//**here I get the OutOfMemoreyException**
        }

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            long chkDigitOdd;
            long chkDigitEven;
            long chkDigitSubtotal;
            chkDigitOdd = Convert.ToInt64(_UPCNumberIncrement.Substring(0, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(2, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(4, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(6, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(8, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(10, 1));
            chkDigitOdd = (3 * chkDigitOdd);
            chkDigitEven = Convert.ToInt64(_UPCNumberIncrement.Substring(1, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(3, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(5, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(7, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(9, 1));
            chkDigitSubtotal = (300 - (chkDigitEven + chkDigitOdd));
            _chkDigit = chkDigitSubtotal.ToString();
            _chkDigit = _chkDigit.Substring(_chkDigit.Length - 1, 1);
            ItemNumberList.Add(new Codes("", "",_chkDigit, "", ""));
        }

リストを使用してこれを行う正しい方法ですか、別の方法を検討する必要がありますか?


リストを使用しても問題ありません。リストに追加しながらリストを繰り返すことは、コードを爆破する確実な方法であり、ロジック(またはコード記述)に問題があることを示します。また、これはあなたのバグであり、将来の訪問者を助ける可能性は低いです。終了する投票。
テラスティン

2
補足として、これらのプライベートフィールド(Codeクラス内)はすべて冗長であり、実際に{ get; private set; }はノイズだけで十分です。
コンラッドモラウスキー

5
この質問のタイトルは本当に正確ですか?これは実際にはリスト対配列の質問のようには見えませんが、実装の質問を改善するにどうすればいいですか。つまり、要素を追加または削除する場合は、リスト(または他の柔軟なデータ構造)が必要です。配列は、最初に必要な要素の数が正確にわかっている場合にのみ本当に優れています。
KChaloux

@KChaloux、それは私が知りたかったことです。リストはこれを行う正しい方法ですか、またはこれを追求する別の方法を見るべきですか?だからリストは良い方法だと思う、私は自分のロジックを調整する必要があります。
campagnolo_1

1
@Telastyn、私は自分がやろうとしていることを示すためにコードを改善することを求めていませんでした。
campagnolo_1

回答:


73

コメントを拡大します。

... 要素を追加または削除する場合は、リスト(または他の柔軟なデータ構造)が必要です。配列は、最初に必要な要素の数が正確にわかっている場合にのみ本当に優れています。

簡単な内訳

配列は、変更される可能性が低い要素の数が固定されており、非順次的な方法でアクセスしたい場合に適しています。

  • 固定サイズ
  • 高速アクセス-O(1)
  • Slow Resize-O(n)-すべての要素を新しい配列にコピーする必要があります!

リンクリストは、両端での迅速な追加と削除に最適化されていますが、途中でのアクセスが遅くなります。

  • 可変サイズ
  • 中央での低速アクセス-O(n)
    • 目的のインデックスに到達するために、先頭から開始して各要素をトラバースする必要があります
  • ヘッドでの高速アクセス-O(1)
  • Tailでの潜在的に高速なアクセス
    • O(1)参照が末尾に格納されている場合(二重リンクリストと同様)
    • 参照が保存されていない場合はO(n)(中央のノードにアクセスするのと同じ複雑さ)

配列リストList<T>C#!など)はこの2つの組み合わせで、かなり高速に追加されランダムアクセスが行われます。List<T> 何を使うべきかわからないとき、あなたの頼りになるコレクションになることがよくあります。

  • 配列をバッキング構造として使用します
  • サイズ変更については賢明です-現在のスペースの2倍を使い果たしたときに割り当てます。
    • これはO(log n)のサイズ変更につながります。これは、追加/削除するたびにサイズ変更するよりも優れています
  • 高速アクセス-O(1)

配列の仕組み

ほとんどの言語は、メモリ内の連続したデータとして配列をモデル化し、各要素のサイズは同じです。intsの配列があったとしましょう([address:value]として表示されます。私は怠け者なので10進数のアドレスを使用しています)

[0: 10][32: 20][64: 30][96: 40][128: 50][160: 60]

これらの各要素は32ビット整数であるため、メモリ内で占めるスペースの大きさ(32ビット!)がわかります。そして、最初の要素へのポインタのメモリアドレスを知っています。

その配列内の他の要素の値を取得するのは簡単です:

  • 最初の要素のアドレスを取得します
  • 各要素のオフセットを取得します(メモリ内のサイズ)
  • オフセットに目的のインデックスを掛けます
  • 結果を最初の要素のアドレスに追加します

最初の要素が「0」にあるとします。2番目の要素は '32'(0 +(32 * 1))であり、3番目の要素は64(0 +(32 * 2))であることを知っています。

これらの値をすべてメモリに隣接して格納できるという事実は、配列が可能な限りコンパクトであることを意味します。また、仕事を続けるには、すべての要素が一緒にいる必要があるということです!

要素を追加または削除したらすぐに、他のすべてをピックアップし、それらをメモリ内の新しい場所にコピーして、要素間にギャップがなく、すべてに十分なスペースがあることを確認する必要があります。これは、特に 1つの要素を追加するたびに実行している場合、非常に遅くなる可能性があります。

リンクリスト

配列とは異なり、リンクリストでは、すべての要素がメモリ内で隣り合っている必要はありません。これらはノードで構成され、次の情報を保存します。

Node<T> {
    Value : T      // Value of this element in the list
    Next : Node<T> // Reference to the node that comes next
}

リスト自体は、ほとんどの場合、先頭末尾(最初と最後のノード)への参照を保持し、場合によってはそのサイズを追跡します。

リストの最後に要素を追加する場合、必要なのはtailを取得し、その値を含むNext新しいNode値を参照するように変更することだけです。末尾から削除するのも同様に簡単です- Next前のノードの値を逆参照するだけです。

残念ながら、LinkedList<T>要素が1000個あり、要素500が必要な場合、配列のように500番目の要素にジャンプする簡単な方法はありません。あなたはheadから始めて、Nextノードに行き続ける必要があります、それを500回するまで。

これが、LinkedList<T>(両端で作業するとき)の追加と削除が高速であるのに、中央へのアクセスが遅い理由です。

編集:ブライアンは、リンクされたリストが連続したメモリに保存されていないため、ページフォルトを引き起こすリスクがあるとコメントで指摘しています。これはベンチマークするのが難しい場合があり、リンクされたリストは、時間の複雑さを考えると、予想よりも少し遅くなる可能性があります。

両方の長所

List<T>両方のための妥協T[]LinkedList<T>し、合理的に高速で、ほとんどの状況で使いやすいソリューションを思い付きます。

内部的にList<T>は配列です!サイズ変更時に要素をコピーするというフープを飛び越えなければなりませんが、いくつかの巧妙なトリックを引き出します。

まず最初に、単一の要素を追加しても、配列がコピーされることは通常ありません。List<T>より多くの要素のために常に十分なスペースがあることを確認します。不足すると、新しい要素を1つだけ使用して新しい内部配列を割り当てる代わりに、いくつかの新しい要素を使用して新しい配列を割り当てます(多くの場合、現在保持されている数の 2倍です!)。

コピー操作はコストがかかるため、List<T>可能な限り削減し、高速なランダムアクセスを許可します。副作用として、まっすぐな配列やリンクリストよりもわずかに多くのスペースを浪費することになりますが、通常はトレードオフの価値があります。

TL; DR

を使用しList<T>ます。それは通常あなたが望むものであり、この状況(.Add()を呼び出している場所)であなたにとって正しいようです。必要なものがわからない場合List<T>は、開始するのに適した場所です。

配列は、「X要素が正確に必要なことを知っている」という高性能に適しています。あるいは、それらは、「一度に定義したこれらのXの項目をグループ化する必要があるので、それらをループできるようにする」構造をすばやく1回限り使用するのに役立ちます。

他にも多くのコレクションクラスがあります。Stack<T>上からのみ動作するリンクリストのようなものです。Queue<T>先入れ先出しリストとして機能します。Dictionary<T, U>キーと値の間の順序付けられていない連想マッピングです。彼らと遊んで、それぞれの長所と短所を知りましょう。彼らはあなたのアルゴリズムを作ったり壊したりすることができます。


2
場合によっては、配列とint使用可能な要素の数を示す組み合わせを使用すると利点がある場合があります。とりわけ、ある配列から別の配列に複数の要素を一括コピーすることは可能ですが、一般的にリスト間でコピーするには要素を個別に処理する必要があります。さらに、配列要素はのrefようなものに渡すことができますがInterlocked.CompareExchange、リストアイテムは渡すことができません。
supercat

2
複数の賛成票を投じることができればいいのですが。ユースケースの違いと、配列/リンクリストがどのようにList<>機能するかは知っていましたが、内部でどのように機能するかについては知りも考えもしませんでした。
ボブソン

1
List <T>に単一の要素を追加すると、O(1)が償却されます。通常、要素を追加する効率は、リンクリストを使用するのに十分な正当化ではありません(また、円形リストを使用すると、償却O(1)でフロントANDバックに追加できます)。リンクリストには、多くのパフォーマンス特性があります。たとえば、メモリに連続して保存されていないということは、リンクされたリスト全体を繰り返し処理することでページフォールトが発生する可能性が高いことを意味します。リンクリストを使用する大きな理由は、2つのリストを連結する(O(1)で実行可能)か、中間に要素を追加する場合です。
ブライアン

1
明確にする必要があります。循環リストとは、循環リンクリストではなく、循環配列リストを意味します。正しい用語はdeque(両端キュー)です。それらは、リスト(内部の配列)とほとんど同じ方法で実装されることがよくありますが、1つの例外があります。最初の要素である配列のインデックスを示す内部整数値 "first"があります。後ろに要素を追加するには、「最初」から1を引くだけです(必要に応じて配列の長さに折り返します)。要素にアクセスするには、にアクセスします(index+first)%length
ブライアン

2
Listではできないこと、プレーンな配列ではできないことがいくつかあります。たとえば、インデックス要素をrefパラメーターとして渡すことです。
イアンゴールドビー

6

一方でKChalouxの答えは素晴らしいですが、私は別の配慮を指摘したいと思います:List<T>多くの強力な配列を超えています。のメソッドはList<T>多くの状況で非常に役立ちます-配列にはこれらのメソッドがなく、回避策を実装するのに多くの時間を費やす可能性があります。

そのため、開発の観点からは、List<T>追加の要件がある場合、ほとんどの場合、を使用している場合に実装する方がはるかに簡単なので、ほとんど常に使用しますList<T>

これは最終的な問題につながります:私のコード(私はあなたのコードは知りません)に90%が含まれているList<T>ため、配列は実際には適合しません。それらを渡すとき、.toList()メソッドを呼び出してリストに変換する必要があります-これ迷惑であり、非常に遅いため、配列の使用によるパフォーマンスの向上は失われます。


確かに、これはList <T>を使用するもう1つの理由です。クラスに直接組み込まれた機能が多く提供されます。
KChaloux

1
LINQは、IEnumerable(配列を含む)に多くの機能を追加することでフィールドを平準化しませんか?現代のC#(4+)には、配列ではなくList <T>でしかできないことはありますか?
デイブ

1
配列/リストを拡張する@Daveはそのようなことのようです。また、リストを構築/処理する構文は、配列の構文よりも優れていると思います。
クリスチャンザウアー14年

2

しかし、この部分については誰も言及していませんでした:「そして、ここですでにメモリ不足の例外が発生しています。」これは完全に原因です

for (int i = 0; i < ItemNumberList.Count; i++)
{
    ItemNumberList.Add(new item());
}

理由は明らかです。別のリストに追加するのかItemNumberList.Count、ループの前に変数として保存するだけで目的の結果が得られるのかわかりませんが、これは単純に壊れています。

Programmers.SEは「...ソフトウェア開発に関する概念的な質問に興味がある...」ためのものであり、他の回答はそれをそのように扱いました。代わりに、http://codereview.stackexchange.comを試してください。この質問に当てはまります。しかし、それでも恐ろしいです。このコードはで始まるとしか想定できないので、エラーが発生したと言う場所_Clickへの呼び出しはmultiValue1ありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.