Чи слід використовувати список чи масив?


22

Я працюю над формою Windows, щоб обчислити UPC для номерів елементів.

Я успішно створюю той, який буде обробляти один номер елемента / 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, "", ""));
        }

Це правильний шлях для вирішення цього питання, використовуючи список, чи я повинен дивитись на інший шлях?


Використання списку чудово. Ітерація списку під час додавання до нього є надійним способом підірвати ваш код і вказує на проблему у вашій логіці (або написанні коду). Крім того, це ваша помилка і, швидше за все, не допоможе майбутнім відвідувачам. Голосування про закриття.
Теластин

2
Зауважимо, що всі ці приватні поля (у вашому Codeкласі) є зайвими, і нічого, крім шуму, насправді { get; private set; }не вистачить.
Конрад Моравський

5
Чи справді назва запитання для цього справді точна? Це насправді не схоже на питання щодо списку проти масиву , настільки, як як я можу покращити моє питання щодо реалізації . Це означає , що якщо ви додаєте або видаляєте елементи, вам потрібен список (або інша гнучка структура даних). Масиви справді хороші лише тоді, коли ви точно знаєте, скільки елементів вам потрібно на старті.
KChaloux

@KChaloux, це майже все, що я хотів знати. Чи є перелік правильним шляхом для цього, чи я повинен був розглянути інший спосіб досягти цього? Тож здається, що список є гарним способом, я просто повинен коригувати свою логіку.
kampgnolo_1

1
@Telastyn, я не так просив покращити свій код, як показати, що я намагаюся зробити.
kampgnolo_1

Відповіді:


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 (показаний як [address: value], використовуючи десяткові адреси, тому що я лінивий)

[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
}

Сам список зберігає посилання на голову та хвіст (перший та останній вузли) у більшості випадків, а іноді відстежує його розмір.

Якщо ви хочете додати елемент до кінця списку, все, що вам потрібно зробити, це отримати хвіст і змінити його Nextна посилання на новий, Nodeщо містить ваше значення. Видалити з кінця так само просто - просто зменшення Nextзначення попереднього вузла.

На жаль, якщо у вас є LinkedList<T>1000 елементів, і ви хочете, щоб елемент 500, не існує простого способу перейти прямо до 500-го елемента, як це є з масивом. Починати потрібно з голови і продовжувати йти до Nextвузла, поки ви не зробите це 500 разів.

Ось чому додавання та видалення з a LinkedList<T>швидко проходить (при роботі на кінцях), але доступ до середини відбувається повільно.

Редагувати : Брайан в коментарях вказує, що пов'язані списки мають загрозу виникнення помилок на сторінці, через те, що вони не зберігаються у суміжній пам'яті. Це може бути важким для порівняння, і може зробити пов'язані списки навіть трохи повільнішими, ніж ви могли очікувати, враховуючи лише їх складні часові труднощі.

Найкраще з обох світів

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<>працювати під кришкою.
Бобсон

1
Додавання одного елемента до списку <T> амортизується O (1); ефективність додавання елементів зазвичай не є достатнім обґрунтуванням використання зв'язаного списку (а круговий список дозволяє додавати передню частину І назад в амортизованому O (1)). Зв'язані списки мають велику кількість ідіосинкрасій ефективності. Наприклад, невпинне зберігання в пам'яті означає, що повторне пересування по всьому пов'язаному списку швидше викликає помилку сторінки ... і це важко для порівняння. Більш виправданим для використання пов'язаного списку є те, коли ви хочете об'єднати два списки (це можна зробити в O (1)) або додати елементи до середини.
Брайан

1
Я повинен уточнити. Коли я сказав круговий список, я мав на увазі список кругового масиву, а не круговий зв'язаний список. Правильний термін буде deque (дворядна черга). Вони часто реалізуються майже так само, як List (масив під кришкою), за винятком: Існує внутрішнє ціле значення, "перше", яке вказує, який індекс масиву є першим елементом. Щоб додати елемент на зворотній частині, ви просто віднімете 1 від "першого" (оберніть навколо довжини масиву, якщо необхідно). Щоб отримати доступ до елемента, ви просто отримаєте доступ (index+first)%length.
Брайан

2
Є кілька речей, які ви не можете зробити зі списком, які ви можете зробити з простого масиву, наприклад, передаючи елемент індексу як параметр ref.
Ян Голдбі

6

Хоча відповідь KChaloux чудова, я хотів би зазначити ще одне враження : List<T>це набагато потужніше, ніж масив. Методи List<T>дуже корисні за багатьох обставин - у масиві немає цих методів, і ви можете витратити багато часу на реалізацію обхідних шляхів.

Отже, з точки зору розвитку я майже завжди використовую, List<T>оскільки, коли є додаткові вимоги, їх часто набагато простіше реалізувати, коли ти використовуєш List<T>.

Це призводить до остаточної проблеми: Мій код (я не знаю про ваш) містить 90% List<T>, тому масиви насправді не підходять. Коли я передаю їх, я повинен викликати їх .toList()метод і перетворити їх у список - це дратує і настільки повільно, що будь-який приріст продуктивності від використання масиву втрачається.


Це правда, це ще одна вагома причина використовувати List <T> - це забезпечує набагато більше функціональних можливостей для вас, вбудованих безпосередньо в клас.
KChaloux

1
Чи не LINQ розширює поле, додаючи багато цієї функціональності для будь-якого IEnumerable (включаючи масив)? Чи залишилось у сучасному C # (4+) щось, що ви можете робити лише зі списком <T>, а не з масивом?
Дейв

1
@Dave Розширення масиву / списку здається такою. Крім того, я б сказав, що синтаксис для побудови / обробки списку набагато приємніший, ніж для масивів.
Крістіан Зауер

2

Ніхто не згадував цю частину: "І ось я вже отримую виняток із пам'яті". Що цілком обумовлено

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

Зрозуміло, чому. Я не знаю, чи ви мали намір додати до іншого списку, чи просто слід зберігати ItemNumberList.Countяк змінну перед циклом, щоб отримати бажаний результат, але це просто порушено.

Programmers.SE - це "... зацікавлений у концептуальних питаннях щодо розробки програмного забезпечення ...", а інші відповіді трактували це як таке. Спробуйте замість http://codereview.stackexchange.com , де це питання відповідатиме. Але навіть там це жахливо, тому що ми можемо лише припустити, що цей код починається з того _Click, що не має дзвінка туди, multiValue1де ви говорите, що сталася помилка.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.