목록이나 배열을 사용해야합니까?


22

항목 번호에 대한 UPC를 계산하기 위해 Windows 양식을 작성 중입니다.

한 번에 하나의 품목 번호 / 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; }
    }

}

그런 다음 코드를 시작했지만 문제는 프로세스가 점진적이라는 것입니다. 즉, 체크 박스를 통해 gridview에서 항목 번호를 가져 와서 목록에 넣습니다. 그런 다음 데이터베이스에서 마지막 UPC를 가져 와서 체크 디지트를 제거한 다음 숫자를 하나씩 늘리고 목록에 넣습니다. 그런 다음 새 번호의 체크 숫자를 계산하여 목록에 넣습니다. 그리고 여기에 이미 메모리 부족 예외가 발생합니다. 지금까지 내가 가지고있는 코드는 다음과 같습니다.

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

이것이 목록을 사용하여 그것에 관한 올바른 방법입니까, 아니면 다른 방법을보고해야합니까?


목록을 사용하는 것이 좋습니다. 추가하는 동안 목록을 반복하는 것은 코드를 폭파시키는 확실한 방법이며 논리 (또는 코드 작성)에 문제가 있음을 나타냅니다. 또한 이것은 버그이며 향후 방문자를 도울 가능성이 없습니다. 마감 투표.
Telastyn

2
부수적으로, (귀하의 Code클래스에서) 이러한 모든 개인 필드 는 중복되며 실제로 소음 { get; private set; }만으로 충분합니다.
Konrad Morawski

5
이것에 대한 질문 제목이 정말 정확합니까? 이것은 실제로 구현 질문을 개선 하는 방법 만큼 목록 대 배열 질문 처럼 보이지 않습니다 . 즉, 요소를 추가 하거나 제거 하는 경우 목록 (또는 다른 유연한 데이터 구조)을 원합니다. 배열은 처음에 필요한 요소 수를 정확히 알고있을 때만 유용합니다.
KChaloux

@ KChaloux, 그것은 내가 알고 싶었던 것입니다. 목록이 이것에 관한 올바른 방법입니까, 아니면 이것을 추구하는 다른 방법을 보았어야합니까? 목록이 좋은 방법 인 것 같습니다. 논리를 조정하면됩니다.
campagnolo_1 '12

1
@Telastyn, 나는 내가하려는 일을 보여주기 위해 내 코드를 개선하도록 요구하지 않았습니다.
campagnolo_1 '12

답변:


73

내 의견을 확장하겠습니다.

... 요소를 추가 하거나 제거 하는 경우 목록 (또는 기타 유연한 데이터 구조)이 필요합니다. 배열은 처음에 필요한 요소 수를 정확히 알고있을 때만 유용합니다.

빠른 고장

배열 은 변경되지 않는 고정 된 수의 요소 가 있고 비 순차적으로 액세스하려는 경우에 유용합니다 .

  • 고정 크기
  • 빠른 액세스-O (1)
  • 느린 크기 조정-O (n)-모든 요소를 ​​새 배열로 복사해야합니다!

연결된 목록 은 양쪽 끝에 빠르게 추가하고 제거 할 수 있도록 최적화되어 있지만 중간에는 액세스 속도가 느립니다.

  • 가변 크기
  • 중간에서 느린 액세스-O (n)
    • 원하는 인덱스에 도달하기 위해 헤드부터 시작하여 각 요소를 순회해야 함
  • 헤드 빠른 접근-O (1)
  • Tail에서 잠재적으로 빠른 액세스
    • O (1) 참조가 꼬리 끝에 저장되어있는 경우 (이중 연결 목록과 같이)
    • 참조가 저장되지 않은 경우 O (n) (중간에있는 노드에 액세스하는 것과 동일한 복잡성)

배열 목록 (예 : List<T>C #!)은 상당히 빠른 추가 임의 액세스 로이 둘을 혼합 한 것입니다 . List<T> 무엇을 사용해야할지 잘 모를 때 종종 컬렉션으로 사용됩니다.

  • 배열을 백업 구조로 사용
  • 크기 조정이 현명합니다. 공간이 부족할 때 현재 공간의 두 배를 할당합니다.
    • 이로 인해 O (log n) 크기 조정이 발생하여 추가 / 제거 할 때마다 크기를 조정하는 것보다 낫습니다.
  • 빠른 액세스-O (1)

배열 작동 방식

대부분의 언어는 배열을 메모리의 연속 데이터로 모델링하며 각 요소의 크기는 같습니다. 우리가 ints 의 배열을 가지고 있다고 가정 해 봅시다 ([주소 : 값]으로 표시됩니다.

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

이러한 각 요소는 32 비트 정수이므로 메모리에서 차지하는 공간 (32 비트)을 알고 있습니다. 그리고 첫 번째 요소에 대한 포인터의 메모리 주소를 알고 있습니다.

해당 배열의 다른 요소의 가치를 얻는 것은 사소한 일입니다.

  • 첫 번째 요소 의 주소
  • 각 요소 의 오프셋 을 가져옵니다 (메모리의 크기)
  • 오프셋에 원하는 인덱스를 하십시오
  • 첫 번째 요소의 주소에 결과를 추가하십시오

첫 번째 요소가 '0'이라고 가정 해 봅시다. 우리는 두 번째 요소가 '32'(0 + (32 * 1))이고 세 번째 요소가 64 (0 + (32 * 2))임을 알고 있습니다.

이러한 모든 값을 메모리에 나란히 저장할 수 있다는 사실은 배열이 가능한 한 컴팩트하다는 것을 의미합니다. 그것은 또한 우리의 모든 요소를해야한다는 것을 의미 유지 작업을 계속하는 것을 함께!

요소를 추가하거나 제거하자마자 다른 모든 요소를 ​​집어 메모리의 새로운 위치로 복사하여 요소 사이에 틈이없고 모든 공간이 충분해야합니다. 이 될 수 있습니다 매우 느린 , 특히 당신이 당신이 하나의 요소를 추가 할 때마다 일을하는 경우.

연결된 목록

배열과 달리, 링크 된 목록은 모든 요소가 메모리에서 서로 옆에있을 필요는 없습니다. 이들은 다음 정보를 저장하는 노드로 구성됩니다.

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 에서 시작하여 Next500 번 할 때까지 계속 노드로 이동해야합니다 .

그렇기 때문에 a에서 추가 및 제거 LinkedList<T>가 빠르지 만 (끝에서 작업 할 때) 중간에 액세스하는 속도가 느립니다.

편집 : Brian은 연속 된 메모리에 저장되지 않기 때문에 링크 된 목록이 페이지 오류를 일으킬 위험이 있다는 의견에서 지적합니다. 이것은 벤치마킹하기 어려울 수 있으며, 시간이 복잡 할 때 예상보다 링크 목록을 약간 느리게 만들 수 있습니다.

두 세계의 최고

List<T>모두 타협 T[]LinkedList<T>대부분의 상황에서 사용하기에 빠르고 쉽게 합리적으로의 해결책을 제공됩니다.

내부적 List<T>으로 배열입니다! 크기를 조정할 때 여전히 요소를 복사하는 과정을 뛰어 넘어야하지만 깔끔한 트릭이 필요합니다.

우선 단일 요소를 추가해도 일반적으로 배열이 복사되지 않습니다. List<T>더 많은 요소를 넣을 공간이 항상 충분한 지 확인하십시오. 새 요소가 하나만 있는 새로운 내부 배열을 할당하는 대신 , 여러 개의 새 요소가 있는 새 배열을 할당합니다 (보통 현재 보유한 것의 두 배).

복사 작업은 비용이 많이 들기 List<T>때문에 빠른 임의 액세스를 허용하면서 가능한 한 많이 줄입니다. 부작용으로 일직선 배열이나 연결된 목록보다 약간 더 많은 공간을 낭비 할 수 있지만 일반적으로 그만한 가치가 있습니다.

TL; DR

사용하십시오 List<T>. 일반적으로 원하는 것입니다.이 상황에서 .Add ()를 호출하는 것이 올바른 것 같습니다. 필요한 것이 확실하지 않으면 List<T>시작하기에 좋은 곳입니다.

배열은 고성능, "정확히 X 요소가 필요하다는 것을 알고 있습니다"에 좋습니다. 대안으로, 그것들은 "일회 정의한이 X 것들을 그룹화하여 그것들을 반복 할 수 있도록"신속하고 일회성으로 유용합니다.

다른 컬렉션 클래스가 많이 있습니다. Stack<T>맨 위에서 만 작동하는 연결된 목록과 같습니다. Queue<T>선입 선출 목록으로 작동합니다. Dictionary<T, U>키와 값 사이의 정렬되지 않은 연관 매핑입니다. 그들과 함께 플레이하고 각각의 장단점을 알 수 있습니다. 알고리즘을 만들거나 깨뜨릴 수 있습니다.


2
경우에 따라 배열과 int사용 가능한 요소 수를 나타내는 조합을 사용하면 이점이있을 수 있습니다 . 무엇보다도 하나의 배열에서 다른 배열로 여러 요소를 대량 복사 할 수 있지만 목록 사이를 복사하려면 일반적으로 개별 요소를 처리해야합니다. 또한 배열 요소는 ref과 같은 것으로 전달 될 수 Interlocked.CompareExchange있지만 목록 항목은 전달할 수 없습니다.
supercat

2
나는 둘 이상의 공감대를 줄 수 있으면 좋겠다. 유스 케이스의 차이점과 배열 / 링크 된 목록의 작동 방식을 알았지 만 List<>후드에서 어떻게 작동 하는지 전혀 알지 못했습니다 .
Bobson

1
List <T>에 단일 요소를 추가하는 것은 상각됩니다. O (1); 요소를 추가하는 효율성은 일반적으로 연결된 목록을 사용하기에 충분한 정당화가 아니며 원형 목록을 사용하면 상각 된 O (1)에서 앞뒤로 추가 할 수 있습니다. 연결된 목록에는 많은 성능 특성이 있습니다. 예를 들어, 메모리에 연속적으로 저장되지 않으면 전체 링크 된 목록을 반복 할 경우 페이지 오류가 발생할 가능성이 높아져 벤치마킹하기가 어렵습니다. 연결리스트 사용에 대한 더 큰 근거는 두리스트를 연결하거나 (O (1)에서 수행 가능) 중간에 요소를 추가하려는 경우입니다.
Brian

1
명확히해야합니다. 순환 목록을 말했을 때 순환 연결 목록이 아닌 순환 배열 목록을 의미했습니다. 올바른 용어는 deque (double-ended queue)입니다. 그것들은 종종 하나의 예외를 제외하고 List (후드 아래 배열)와 거의 같은 방식으로 구현됩니다 : 배열의 어느 인덱스가 첫 번째 요소인지를 나타내는 "first"내부 정수 값이 있습니다. 뒷면에 요소를 추가하려면 "first"에서 1을 빼면됩니다 (필요한 경우 배열 길이를 감습니다). 요소에 액세스하려면에 액세스하십시오 (index+first)%length.
Brian

2
예를 들어 인덱스 요소를 ref 매개 변수로 전달하는 것과 같이 일반 배열로 할 수있는 List로 할 수없는 몇 가지가 있습니다.
Ian Goldby

6

KChaloux의 대답 은 훌륭 하지만 다른 고려 사항을 지적하고 싶습니다 List<T>. 배열보다 훨씬 강력합니다. 의 메소드는 List<T>많은 상황에서 매우 유용합니다. Array에는 이러한 메소드가 없으므로 해결 방법을 구현하는 데 많은 시간이 소요될 수 있습니다.

따라서 개발 측면에서 볼 List<T>때 추가 요구 사항이있을 때을 사용할 때 구현하기가 훨씬 쉽기 때문에 거의 항상 사용 합니다 List<T>.

이로 인해 최종 문제가 발생합니다. 내 코드 (귀하의 코드를 모릅니다)에 90 %가 포함되어 List<T>있으므로 배열이 실제로 적합하지 않습니다..toList() 방법 및 목록으로 변환 -이 성 가시고 너무 느려서 어레이 사용으로 인한 성능 향상이 손실됩니다.


사실 이것은 List <T>를 사용해야하는 또 다른 좋은 이유입니다. 클래스에 직접 내장 할 수있는 훨씬 더 많은 기능을 제공합니다.
KChaloux

1
LINQ는 IEnumerable (배열 포함)에 대해 많은 기능을 추가하여 필드 레벨을 조정하지 않습니까? 현대 C # (4+)에 배열이 아닌 List <T>로만 할 수있는 것이 있습니까?
Dave

1
@Dave 배열 / 목록 확장은 그런 것 같습니다. 또한 List를 생성 / 처리하는 구문이 배열보다 훨씬 좋습니다.
Christian Sauer

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.