
- •Тема 1 Колекції Структури даних
- •Неузагальнені колекції
- •Деякі інтерфейси неузагальнених колекцій
- •Деякі класи неузагальнених колекцій
- •Узагальнені колекції
- •Тема 2 Файловий ввід-вивід Організація файлового вводу-виводу
- •Байтовий ввід-вивід у файл
- •Двійковий ввід-вивід у файл
- •Символьний ввід-вивід у файл
- •Організація роботи із файлами даних стандарту xml
- •Простір імен System.Xml
- •Класи XmlNode та XmlLinkedNode
- •Клас XmlDeclaration
- •Класи XmlComment та XmlAttribute
- •Клас XmlElement
- •Клас XmlDocument
- •Приклад
- •Тема 3 Делегати та події Клас delegate
- •Делегати в якості параметрів
- •Анонімні методи та узагальнені делегати System.Action, System.Func
- •Комбіновані делегати та шаблон «спостерігач»
- •Події: створення та обробка
Деякі класи неузагальнених колекцій
Клас Stack. Стек відноситься до однієї із найважливіших динамічних структур даних. Ця структура часто застосовується в системному програмному забезпеченні, компіляторах, тощо. Нагадаємо, що стек – це динамічна структура даних, у якій додавання елементів та їх вилучення здійснюється із одного кінця, який називається вершиною стека (головою − head). Інші операції із стеком не визначені. Кажуть, що стек реалізує принцип обслуговування LIFO («last in – first out» – «останнім прийшов – першим вийшов»).
У просторі імен System.Collections колекцією, яка підтримує стек, є клас Stack. Цей клас створює динамічну колекцію, яка розширюється по мірі потреби збереження в ній елементів, які в неї додаються. Кожного разу, коли потрібно розширити таку колекцію, її розмір автоматично збільшується вдвічі. Клас Stack реалізує інтерфейси ICollection, IEnumerable та ICloneable. Окрім методів, оголошених у цих інтерфейсах, у класі Stack визначено ряд власних методів. Наведемо найбільш часто використовувані із них:
Метод класу Stack |
Призначення |
public Stack(); public Stack(int initialCapacity); public Stack(ICollection col); |
У першій формі конструктора створюється порожній стек. У другій − порожній стек, початковий розмір якого задається параметром initialCapacity. У третій − стек, що містить елементи колекції col. |
public virtual void Clear(); |
Очищає колекцію. Після цього значення властивості Count стає рівним нулю. |
public virtual bool Contains(object obj); |
Визначає чи міститься елемент у колекції. Повертає значення true якщо об’єкт obj знайдено в колекції, інакше − false. |
public virtual object Peek(); |
Повертає об’єкт, який знаходиться у вершині стека, але не видаляє його. |
public virtual object Pop(); |
Повертає об’єкт, який знаходиться у вершині стека та видаляє його. |
public virtual void Push(object obj); |
Додає об’єкт obj у вершину стека. |
Стек часто використовується в алгоритмах як допоміжна структура даних у тих випадках, коли потрібно змінити порядок яких-небудь елементів на зворотний. Як приклад розглянемо наступну задачу. Нехай дано ціле додатне число у десятковій системі числення. Потрібно перевести його в задану систему числення. Нагадаємо, що при переводі числа в іншу систему числення проводиться послідовне отримання остач від ділення цього числа на основу нової системи числення, які при записі у зворотному порядку утворюють представлення числа у новій системі числення. Тому остачі від ділення доцільно заносити у стек, а потім вилучати з нього для формування рядкового представлення запису числа. Якщо основа нової системи числення більша за 10, то в рядок заноситься відповідний символ (10 – ‘A’, 11 – ‘B’ і т.д.).
// клас для переводу цілого додатного числа із десяткової системи числення у іншу class Converter { public static string convert(uint number, byte basis) { // стек для збереження остач від ділення Stack Remainders = new Stack(); // обчислення остач від ділення числа на основу нової системи числення // та їх занесення у стек while (number != 0) { Remainders.Push(number % basis); number /= basis; } // формування рядкового представлення запису числа шляхом вилучення значень зі стеку string res = ""; while(Remainders.Count != 0) { uint digit = (uint)Remainders.Pop(); if(digit < 10) res = res + digit; else res = res + (char)((uint)'A' + digit - 10); } return res; } } |
Клас Queue. Ще однією розповсюдженою структурою даних є черга, що діє за принципом: першим прийшов − першим оброблений. Додавання елементів у чергу виконується в один кінець («хвіст»), а вилучення проводиться з іншого кінця («голови»). Інші операції із чергою не визначені. Черга реалізує принцип обслуговування FIFO («first in – first out», першим прийшов – першим вийшов).
У мові C# клас колекції, що підтримує чергу, має назву Queue. Цей клас створює динамічну колекцію, яка розширюється, по мірі збереження у ній елементів. Так, якщо в черзі для збереження елемента потрібне додаткове місце, її розмір збільшується на коефіцієнт росту, який за замовчуванням дорівнює 2. У цьому класі реалізуються інтерфейси ICollection, IEnumerable та IСloneable. Крім методів визначених у цих інтерфейсах у класі Queue є ряд власних методів:
Метод класу Queue |
Призначення |
public Queue (); public Queue (int initialCapacity); public Queue(int capacity, float growFactor); public Queue(ICollection col); |
У першій формі конструктора створюється порожня черга із коефіцієнтом росту 2. У другій − порожня черга, початковий розмір якої задається параметром initialCapacity, та коефіцієнтом росту 2. У третій є можливість вказати не тільки початковий розмір черги, але й її коефіцієнт росту (в якості параметра growFactor у межах від 1.0 до 10.0). Ну і четвертій формі − створюється черга, що містить елементи колекції col. |
public virtual void Clear(); |
Очищає колекцію. Після цього значення властивості Count стає рівним нулю. |
public virtual bool Contains(object obj); |
Визначає чи міститься елемент у колекції. Повертає значення true якщо об’єкт obj знайдено в колекції, інакше − false. |
public virtual object Dequeue(); |
Повертає та видаляє об’єкт із початку черги. |
public virtual void Enqueue(object obj); |
Додає об’єкт obj в кінець черги. |
public virtual object Peek(); |
Повертає об’єкт, який знаходиться на початку черги, але не видаляє його. |
public virtual void TrimToSize(); |
Задає розмір черги у відвовідності до фактичної кількості її елементів. |
Розглянемо приклад роботи із чергою.
Нехай
задано дві черги
|
class Program { static void Main() { Queue X = new Queue((ICollection)(new double[] { 1, 2, 3, 4, 5 })); Queue Y = new Queue((ICollection)(new double[] { 3, 2, 1 })); uint Step = 0; while (X.Count != 0 & Y.Count != 0) { Step++; double x = (double)X.Dequeue(); double y = (double)Y.Dequeue(); if (x < y) X.Enqueue(x + y); else Y.Enqueue(x - y); } Console.WriteLine("step= {0}", Step); Console.ReadKey(); } } |
Клас ArrayList. У мові С# стандартні масиви мають фіксовану довжину. Це означає, що кількість елементів у масиві потрібно задавати заздалегідь, що не зовсім ефективно з точки зору використання пам’яті. Клас ArrayList призначений для випадків, коли кількість елементів масиву не можна визначити наперед або ж, коли кількість елементів буде змінюватись під час виконання програми. Цей клас визначає масив змінної довжини, який складається з посилань на об’єкти та може динамічно збільшувати та зменшувати свій розмір. Колекції класу ArrayList широко використовуються в практиці програмування на С#.
У класі ArrayList реалізуються інтерфейси IСollection, IList, IEnumerable та IСloneable. У класі ArrayList визначається ряд власних методів, окрім тих, що вже оголошені в цих інтерфейсах. Деякі з найбільше часто використовуваних методів класу ArrayList:
Метод класу ArrayList |
Призначення |
public ArrayList(); public ArrayList(int capacity); public ArrayList(ICollection c); |
У першій формі конструктора створюється порожній екземпляр із початковим нульовим розміром. У другій − порожній екземпляр, початковий розмір якого задається параметром сapacity. У третій − екземпляр, що містить елементи колекції c. |
public virtual int Capacity { get; set; } |
Дозволяє отримувати та всановлювати розмір динамічного масиву. |
public virtual void TrimToSize(); |
Задає розмір динамічного масиву у відвовідності до кількості фактичних елементів масиву, тобто встановлює значення властивості Capacity рівним значенню властивості Count. |
public virtual void AddRange(ICollection c); |
Додає у кінець динамічного масиву елементи із колекції с. |
public virtual void InsertRange(int index, ICollection c); |
Вставляє у динамічний масив елементи колекції c, починаючи з елемента із індексом index. |
public virtual void SetRange(int index, ICollection c); |
Замінює частину динамічного масиву, починаючи з елемента з індексом index, елементами колекції c. |
public virtual void RemoveRange(int index, int count); |
Видаляє count елементів динамічного масиву, починаючи з елемента, що задається індексом index. |
public virtual ArrayList GetRange(int index, int count); |
Повертає частину динамічного масиву, посинаючи із елемента з індексом index та включає кількість елементів, що задається параметром count. Об’єкт, що повертається містить посилання на ті ж самі елементи, що і вихідний об’єкт. |
public virtual void Reverse(); public virtual void Reverse(int index, int count); |
У першому випадку метод розташовує елементи динамічного масиву в оберненому порядку. У другому − розташовує в оберненому порядку тільки перші count елементів, які розташовані після елементу з індексом index. |
public virtual void Sort(); public virtual void Sort(IComparer comparer); public virtual void Sort(int index, int count, IComparer comparer); |
Перший метод сортує динамічний масив за зростанням. Другий − сортує за зростанням динамічний масив, використовуючи для порівняння спосіб, який відповідає параметру comparer. Третій − робить те саме, що й другий, тільки сортуються перші count елементів, починаючи із елемента з індексом index. |
public virtual int BinarySearch(object value); public virtual int BinarySearch(object value, IComparer comparer); public virtual int BinarySearch(int index, int count, object value, IComparer comparer); |
Здійсню пошук значення value у динамічному масиві. Повертає індекс знайденогого елемента. Якщо значення не знайдено, то повертає від’ємне значення. Динамічний масив має бути відсортованим. У другому методі для порівняння використовується сосіб у відповідності до параметру comparer. У третьому методі пошук ведеться серед перших count елементів, починаючи із елемента з індексом index. |
Наведемо приклад використання та особливості деяких із наведених можливостей. Нехай задано масив цілих чисел, серед яких немає однакових. Цей масив у загальному випадку ділиться на три частини двома елементами: мінімальним та максимальним. Потрібно виконати над масивом наступну послідовність дій:
додати у кінець масиву його першу частину;
видалити другу частину;
вставити на початок третю частину.
class VarAarray { // динамічний масив ArrayList array; // конструктор з параметрами public VarAarray(params object[] args) { array = new ArrayList((ICollection)args); } // вивід на екран всіх елементів колекції заданого типу public void Display() { Console.WriteLine("[ {0}/{1}", array.Count, array.Capacity); foreach (object o in array) Console.Write(" {0}",((int)o).ToString()); Console.WriteLine("\n]"); } //метод розвязання задачі public void Solve() { // визначення індексів найбільшого та найменшого значення масиву int imin =0; int imax =0; for (int i = 1; i < array.Count; i++ ) { if ((int)array[i] < (int)array[imin]) imin = i; if ((int)array[i] > (int)array[imax]) imax = i; } // впорядковуємо знайдені індекси int i1 = imin; int i2 = imax; if (i1 > i2) { i1 = imax; i2 = imin; } // визначаємо кількість елементів у третій частині int count3 = array.Count - i2 - 1; // додаємо у кінець масиву першу частину ArrayList p1 = array.GetRange(0, i1); // визначаємо першу частину масиву array.AddRange(p1);// додаємо її у кінець масиву array.TrimToSize();// приводимо розмір масиву до фактичної кількості елементів // видяляємо другу частину array.RemoveRange(i1 + 1, i2 - i1 - 1); array.TrimToSize(); // вставляємо на початок третю частину ArrayList p3 = array.GetRange(i2, count3); array.InsertRange(0, p3); array.TrimToSize(); } } |
Клас Hashtable призначений для створення колекції, у якій для зберігання її елементів служить хеш-таблиця. Ця структура даних, яка є програмною реалізацією асоціативного масиву (словник) − абстрактного типу даних, який дозволяє зберігати пари виду «(ключ, значення)» та підтримує операції додавання пари, пошуку за ключем та видалення за ключем. Основою реалізації хеш-таблиці є механізм хешування, який полягає у визначенні унікального значення (хеш-коду), на основі значення відповідного ключа. Отриманий хеш-код служить у якості індекса, за яким у таблиці зберігаються значення (дані), які відповідають заданому ключу. Перетворення ключа в хеш-код у мові C# виконується автоматично.
У класі Hashtable реалізуються інтерфейси IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback і ICloneable. У класі Hashtable визначається ряд власних методів, окрім тих, що вже оголошені в цих інтерфейсах. Деякі з найбільше часто використовуваних методів класу Hashtable:
Метод класу Hashtable |
Призначення |
public Hashtable(); public Hashtable(IDictionary d); public Hashtable(int capacity); |
У першій формі конструктора створюється об’єкт класу Hashtable за замовчуванням. У другій формі створюваний об’єкт типу Hashtable ініціалізується елементами із колекції d. У третій формі створюваний об’єкт типу Hashtable ініціалізується, із початковим розміром, що задається параметром capacity. |
public virtual bool ContainsKey(object key); |
Повертає true, якщо у словнику міститься пара із заданим ключем key. |
public virtual bool ContainsValue(object value); |
Повертає true, якщо у словнику міститься пара із заданим значенням value. |
public virtual ICollection Keys { get; } |
Повертає всі ключі даного екземпляра словника. |
public virtual ICollection Values { get; } |
Повертає всі значення даного екземпляра словника. |
Даний вид колекції зручно застосовувати тоді, коли дані визначаються деяким ключовим полем. Це поле стає індексом елемента у колекції. У якості ключового поля може бути обраний об’єкт будь-якого типу даних, наприклад, рядок, число або об’єкт деякого класу. Цей тип збереження інформації дозволяє скорочувати час виконання таких операцій, як пошук за ключем, зчитування за ключем та запис даних, навіть для дуже великої кількості елементів.
Найпростішим прикладом асоціативного масиву є телефонний довідник. У цьому випадку, наприклад, ключем може бути номер телефону, а значенням (даними) − ПІБ та адреса. Це програмно можна реалізувати у такий спосіб:
// інформація про абонента struct SubscriberInfo { // прізвище, ім'я, по-батькові string FIO; // адреса string Address; // конструктор з параметрами public SubscriberInfo(string fio, string adress) { FIO = fio; Address =adress; } // перевизначення успадкованого методу перетворення структури у рядок public new string ToString() { return FIO + " " +Address; } } // телефонна книга class PhoneBook : Hashtable { } |
При такій реалізації є можливість зручного та швидкого доступу до даних абонента за його номером телефону. Наприклад:
PhoneBook MyPhoneBook = new PhoneBook(); MyPhoneBook.Add("0509845231", new SubscriberInfo("Сидоренко", "пр.Перемоги, 10")); MyPhoneBook.Add("0959845231", new SubscriberInfo("Петренко", "вул.Мукачівська, 123")); MyPhoneBook.Add("0509844567", new SubscriberInfo("Андрієнко", "вул.Загорська, 15")); if (MyPhoneBook.ContainsKey("0509844567")) Console.WriteLine(((SubscriberInfo)MyPhoneBook["0509844567"]).ToString()); else Console.WriteLine("Абонента не знайдено!"); |
Оскільки клас Hashtable реалізовує інтерфейс IEnumerable, то для перебору елементів колекції може використовуватися оператор foreach. Для перебору у якості локальної змінної цього циклу можна використати об’єкт структури DictionaryEntry із простору імен System.Collections, яка визначає пару «(ключ, значення)» словника.
[Serializable] [ComVisible(true)] public struct DictionaryEntry { public DictionaryEntry(object key, object value); public object Key { get; set; } public object Value { get; set; } } |
Наступний приклад демонструє застосування об’єкта типу DictionaryEntry для виводу всіх елементів екземпляру MyPhoneBook колекції PhoneBook.
foreach (DictionaryEntry de in MyPhoneBook) Console.WriteLine("{0}: {1}", de.Key, ((SubscriberInfo)de.Value).ToString()); |
Альтернативним варіантом перебору може бути перебір на основі колекції ключів словника. У цьому випадку вивід всіх елементів екземпляру MyPhoneBook колекції PhoneBook виглядає так:
foreach (string key in MyPhoneBook.Keys) Console.WriteLine("{0}: {1}", key, ((SubscriberInfo)MyPhoneBook[key]).ToString()); |
На завершення огляду класу Hashtable розглянемо типову задачу, при програмній реалізації якої є доцільним та зручним використання словника. Такою задачею є задача підрахунку кількості входжень заданих об’єктів деякого цілого. Наприклад, нехай задано текст, у якому слова розділені пробілами. Потрібно підрахувати скільки разів зустрічається кожне слово у тексті. Програмна реалізація цієї задачі із використанням об’єкту Hashtable може бути такою:
class Text { // текст string text; // конструктор з параметром public Text(string arg) { text = arg; } // метод підрахунку кількості входжень слів у тексті public void NumOccurrWords() { // асоціативний масив "(слово, кількість входжень)" Hashtable t = new Hashtable(); // копія заданого тексту string tmp = text; // видаляємо всі пробіли у копії на початку та в кінці tmp = tmp.Trim(); // поки текст копії не порожній while (tmp.Length > 0) { string word; // визначаємо індекс першого входження пробіла int i = tmp.IndexOf(' '); // якщо слово у копії тексту не останнє if (i > 0) { // визначаємо чергове слово word = tmp.Substring(0, i); // видаляємо це слово із копії тексту tmp = tmp.Remove(0, i); // видаляємо пробіли, що залишилися на початку копії тексту tmp = tmp.TrimStart(); } // якщо слово у копії тексті останнє else { // копія тексту і є чергове слово word = tmp; // видаляємо весь текст із копії - всі слова прочитано tmp = tmp.Remove(0); } // якщо чергове слово вже зустрічалося if(t.ContainsKey(word)) { // збільшуємо кількість входжень цього слова на одиницю int k = (int)t[word]; t[word] = ++k; } else t.Add(word, 1); } // виводимо дані хеш-таблиці foreach (DictionaryEntry pair in t) Console.WriteLine("{0}: {1}", pair.Key, pair.Value); } } |