web-dev-qa-db-ja.com

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

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

一度に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増やしてリストに入れます。次に、新しい数値のチェックディジットを計算して、そして、ここですでにOut of Memory Exceptionが発生しています。ここまでのコードは次のとおりです。

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, "", ""));
        }

これは、リストを使用してそれを行うのに正しい方法ですか、それとも別の方法を検討すべきですか?

22
campagnolo_1

コメントを拡大します。

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

簡単な内訳

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

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

Linked-Listsは、両端での迅速な追加と削除のために最適化されていますが、途中でのアクセスが低速です。

  • 可変サイズ
  • 途中でアクセスが遅い-O(n)
    • 目的のインデックスに到達するために、先頭から各要素をトラバースする必要があります
  • 頭での高速アクセス-O(1)
  • テールでの高速アクセスの可能性
    • O(1) if a reference is stored at the tail end (as with a doubly-linked list)
    • O(n) if no reference is stored (same complexity as accessing a node in the middle)

配列リスト(C#のList<T>など)は2つの混合で、かなり高速に追加されますandランダムアクセス。 List<T>何を使用するかわからない場合は、多くの場合、頼りになるコレクションになります。

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

配列のしくみ

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

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

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

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

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

最初の要素が「0」にあるとしましょう。 2番目の要素は '32'(0 +(32 * 1))にあり、3つ目の要素は64(0 +(32 * 2))にあることがわかります。

これらの値をすべてメモリ内に並べて保存できるという事実は、配列が可能な限りコンパクトであることを意味します。これは、すべての要素がstayを一緒にして動作し続ける必要があることも意味します!

要素を追加または削除したらすぐに、他のすべてを取得し、それらをメモリ内の新しい場所にコピーして、要素間にギャップがなく、すべてに十分なスペースがあることを確認する必要があります。これはvery slowespeciallyの場合、単一の要素を追加するたびに実行します。

リンクされたリスト

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

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

リスト自体は、ほとんどの場合headおよびtail(最初と最後のノード)への参照を保持し、そのサイズを追跡することもあります。

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

残念ながら、1000個の要素を持つLinkedList<T>があり、要素500が必要な場合、配列のように500番目の要素に直接ジャンプする簡単な方法はありません。 headから始めて、500回完了するまでNextノードに移動する必要があります。

このため、LinkedList<T>の追加と削除は高速ですが(最後で作業する場合)、中間へのアクセスは低速です。

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

両方の長所

List<T>T[]LinkedList<T>の両方に妥協し、ほとんどの状況で適度に高速で使いやすいソリューションを考え出します。

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

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

コピー操作は負荷が高いので、List<T>は高速ランダムアクセスを許可しながら、可能な限りそれらを削減します。副作用として、まっすぐな配列やリンクされたリストよりもわずかに多くのスペースを無駄にしてしまう可能性がありますが、通常はトレードオフの価値があります。

TL; DR

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

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

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

73
KChaloux

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

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

その理由は明らかです。別のリストに追加するつもりなのか、ループの前にItemNumberList.Countを変数として格納して目的の結果を得る必要があるのか​​はわかりませんが、これは単に壊れています。

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

2
Wolfzoon