Хелпикс

Главная

Контакты

Случайная статья





Динамические структуры и классы в С# на основе коллекций: хеш-таблицы.



 

" Исследование логики работы программы, построенной на взаимодействии классов"

Динамические структуры и классы в С# на основе коллекций: хеш-таблицы.

   Ход работы:

1. Изучить теоретические положения о хеш-таблицах и области их применения 2 часа

2. Выполнить тестовый пример по реализации элемента данных хеш-таблицы class Item(string key, string value) пара ключ-значение, сформировать из таких данных таблицу class Hashtable, с выводом, созданием, удалением данных в таблице. См. пример 1. 2 часа

3. Сравнить реализацию тестового примера 1 и готовую реализацию класса коллекций на платформе Net впримере 2, сделать выводы. 2 часа

4. Оформить данную часть работы в отчет по учебной практике, ответить наконтрольные вопросы. 2 часа

 

Хеш-таблица (hashtable) — это структура данных, представляющая собой специальным образом организованный набор элементов хранимых данных. Все данные хранятся в виде пар хеш-значения. Данная структура похожа на словарь (map), но имеет особенности такие как применение хеш-функции для увеличения скорости поиска. Принцип работы данной структуры схож с каталогом книг. Все книги разложены в алфавитном порядке, но не на одном стеллаже, а для каждой буквы выделен отдельный стеллаж, поэтому нам не нужно по порядку перебирать все книги, а можно подойти к нужному стеллажу и искать уже там.

 

Хеш-таблицы применяются в файловых системах операционных систем. Изначально, когда нужно найти имя файла, поиск в каталогах велся линейно, от начала до конца. Линейный поиск в очень длинных каталогах может выполняться довольно медленно. Ускорить поиск поможет присутствие в каждом каталоге хэш-таблицы. Пусть размер такой таблицы будет равен n. При добавлении в каталог нового имени файла оно должно хэшироваться в число от 0 до n – 1, к примеру, путем деления его на n и взятия остатка. В качестве альтернативы можно сложить слова, составляющие имя файла, и получившуюся сумму разделить на n или сделать еще что-либо подобное. Производится отображение символьного имени файла в целое число из требуемого диапазона по некоторому алгоритму, рассматривающему имя как некоторое число (битовую строку) или последовательность чисел (например, кодов символов и т. п. ).

 

В любом случае просматривается элемент таблицы, соответствующий полученному хэш-коду. Если элемент не используется, туда помещается указатель на запись о файле. Эти записи следуют сразу за хэш-таблицей. Если же элемент таблицы уже занят, то создается связанный список, объединяющий все записи о файлах, имеющих одинаковые хэш-коды, и заголовок этого списка помещается в элемент таблицы. При поиске файла производится такая же процедура.

 

Для выбора записи в хэш-таблице имя файла хэшируется. Затем на присутствие имени файла проверяются все записи в цепочке, чей заголовок помещен в элементе таблицы. Если искомое имя файла в этой цепочке отсутствует, значит, в каталоге файла с таким именем нет. Преимущество использования хэш-таблицы состоит в существенном увеличении скорости поиска, а недостаток заключается в усложнении процесса администрирования. Рассматривать применение хэш-таблицы стоит только в тех системах, где ожидается применение каталогов, содержащих сотни или тысячи файлов.

 

Другим способом ускорения поиска в больших каталогах является кэширование результатов поиска. Перед началом поиска проверяется присутствие имени файла в кэше. Если оно там есть, то местонахождение файла может быть определено немедленно. Разумеется, кэширование поможет, только если результаты поиска затрагивают относительно небольшое количество файлов.

 

Хеш-таблица позволяет выполнять три операции: операцию добавления новой пары, операцию поиска и операцию удаления пары по ключу. Важное свойство хеш-таблиц состоит в том, что, при некоторых разумных допущениях, все три операции (поиск, вставка, удаление элементов) в среднем выполняются за время O(1). Но при этом не гарантируется, что время выполнения отдельной операции мало́. Это связано с тем, что при достижении некоторого значения коэффициента заполнения необходимо осуществлять перестройку индекса хеш-таблицы: увеличить значение размера массива и заново добавить в пустую хеш-таблицу все пары.

 

Существуют два основных способа реализации хеш-таблиц:

- Метод цепочек (открытое хеширование) — все элементы данных с совпадающем хешем объединяются в список.

- Метод открытой адресации (закрытое хеширование) — добавляем элемент данных в ячейку по хешу, если эта ячейка занята, то переходим в следующую до тех пор, пока не найдем свободную.

 

Давайте рассмотрим пример 1 реализации хеш-таблицы на языке C#.

 

Для простоты будем рассматривать структуру из пары двух строк. Данная реализация является весьма примитивной, но позволяет разобраться в структуре и алгоритме работы данного типа данных.

 

Элемент данных хеш-таблицы

using System;

 

namespace HashTable

{

/// < summary>

/// Элемент данных хеш таблицы.

/// < /summary>

public class Item

{

   /// < summary>

   /// Ключ.

   /// < /summary>

   public string Key { get; private set; }

 

   /// < summary>

   /// Хранимые данные.

   /// < /summary>

   public string Value { get; private set; }

 

   /// < summary>

   /// Создать новый экземпляр хранимых данных Item.

   /// < /summary>

   /// < param name=" key" > Ключ. < /param>

   /// < param name=" value" > Значение. < /param>

   public Item(string key, string value)

   {

       // Проверяем входные данные на корректность.

       if(string. IsNullOrEmpty(key))

       {

           throw new ArgumentNullException(nameof(key));

       }

 

       if(string. IsNullOrEmpty(value))

       {

           throw new ArgumentNullException(nameof(value));

       }

 

       // Устанавливаем значения.

       Key = key;

       Value = value;

   }

 

   /// < summary>

   /// Приведение объекта к строке.

   /// < /summary>

   /// < returns> Ключ объекта. < /returns>

   public override string ToString()

   {

       return Key;

   }

}

}

Хеш-таблица

using System;

using System. Collections. Generic;

using System. Linq;

 

namespace HashTable

{

/// < summary>

/// Хеш-таблица.

/// < /summary>

/// < remarks>

/// Используется метод цепочек (открытое хеширование).

/// < /remarks>

public class HashTable

{

   /// < summary>

   /// Максимальная длина ключевого поля.

   /// < /summary>

   private readonly byte _maxSize = 255;

 

   /// < summary>

   /// Коллекция хранимых данных.

   /// < /summary>

   /// < remarks>

   /// Представляет собой словарь, ключ которого представляет собой хеш ключа хранимых данных,

   /// а значение это список элементов с одинаковым хешем ключа.

   /// < /remarks>

   private Dictionary< int, List< Item> > _items = null;

 

   /// < summary>

   /// Коллекция хранимых данных в хеш-таблице в виде пар Хеш-Значения.

   /// < /summary>

   public IReadOnlyCollection< KeyValuePair< int, List< Item> > > Items => _items?. ToList()?. AsReadOnly();

 

   /// < summary>

   /// Создать новый экземпляр класса HashTable.

   /// < /summary>

   public HashTable()

   {

       // Инициализируем коллекцию максимальным количество элементов.

       _items = new Dictionary< int, List< Item> > (_maxSize);

   }

 

   /// < summary>

   /// Добавить данные в хеш таблицу.

   /// < /summary>

   /// < param name=" key" > Ключ хранимых данных. < /param>

   /// < param name=" value" > Хранимые данные. < /param>

   public void Insert(string key, string value)

   {

       // Проверяем входные данные на корректность.

       if (string. IsNullOrEmpty(key))

       {

           throw new ArgumentNullException(nameof(key));

       }

 

       if (key. Length > _maxSize)

       {

           throw new ArgumentException($" Максимальная длинна ключа составляет {_maxSize} символов. ", nameof(key));

       }

 

       if (string. IsNullOrEmpty(value))

       {

           throw new ArgumentNullException(nameof(value));

       }

 

       // Создаем новый экземпляр данных.

       var item = new Item(key, value);

 

       // Получаем хеш ключа

       var hash = GetHash(item. Key);

 

       // Получаем коллекцию элементов с таким же хешем ключа.

       // Если коллекция не пустая, значит заначения с таким хешем уже существуют,

       // следовательно добавляем элемент в существующую коллекцию.

       // Иначе коллекция пустая, значит значений с таким хешем ключа ранее не было,

       // следовательно создаем новую пустую коллекцию и добавляем данные.

       List< Item> hashTableItem = null;

       if (_items. ContainsKey(hash))

       {

           // Получаем элемент хеш таблицы.

           hashTableItem = _items[hash];

 

           // Проверяем наличие внутри коллекции значения с полученным ключом.

           // Если такой элемент найден, то сообщаем об ошибке.

           var oldElementWithKey = hashTableItem. SingleOrDefault(i => i. Key == item. Key);

           if (oldElementWithKey! = null)

           {

               throw new ArgumentException($" Хеш-таблица уже содержит элемент с ключом {key}. Ключ должен быть уникален. ", nameof(key));

           }

 

           // Добавляем элемент данных в коллекцию элементов хеш таблицы.

           _items[hash]. Add(item);

       }

       else

       {

           // Создаем новую коллекцию.

           hashTableItem = new List< Item> { item };

 

           // Добавляем данные в таблицу.

           _items. Add(hash, hashTableItem);

       }

   }

 

   /// < summary>

   /// Удалить данные из хеш таблицы по ключу.

   /// < /summary>

   /// < param name=" key" > Ключ. < /param>

   public void Delete(string key)

   {

       // Проверяем входные данные на корректность.

       if (string. IsNullOrEmpty(key))

       {

           throw new ArgumentNullException(nameof(key));

       }

 

       if (key. Length > _maxSize)

       {

           throw new ArgumentException($" Максимальная длинна ключа составляет {_maxSize} символов. ", nameof(key));

       }

 

       // Получаем хеш ключа.

       var hash = GetHash(key);

 

       // Если значения с таким хешем нет в таблице,

       // то завершаем выполнение метода.

       if (! _items. ContainsKey(hash))

       {

           return;

       }

 

       // Получаем коллекцию элементов по хешу ключа.

       var hashTableItem = _items[hash];

 

       // Получаем элемент коллекции по ключу.

       var item = hashTableItem. SingleOrDefault(i => i. Key == key);

 

       // Если элемент коллекции найден,

       // то удаляем его из коллекции.

       if (item! = null)

       {

           hashTableItem. Remove(item);

       }

   }

 

   /// < summary>

   /// Поиск значения по ключу.

   /// < /summary>

   /// < param name=" key" > Ключ. < /param>

   /// < returns> Найденные по ключу хранимые данные. < /returns>

   public string Search(string key)

   {

       // Проверяем входные данные на корректность.

         if (string. IsNullOrEmpty(key))

       {

           throw new ArgumentNullException(nameof(key));

       }

 

       if (key. Length > _maxSize)

       {

           throw new ArgumentException($" Максимальная длинна ключа составляет {_maxSize} символов. ", nameof(key));

       }

 

       // Получаем хеш ключа.

       var hash = GetHash(key);

 

       // Если таблица не содержит такого хеша,

       // то завершаем выполнения метода вернув null.

       if(! _items. ContainsKey(hash))

       {

           return null;

       }

 

       // Если хеш найден, то ищем значение в коллекции по ключу.

       var hashTableItem = _items[hash];

 

       // Если хеш найден, то ищем значение в коллекции по ключу.

       if (hashTableItem! = null)

       {

           // Получаем элемент коллекции по ключу.

           var item = hashTableItem. SingleOrDefault(i => i. Key == key);

 

           // Если элемент коллекции найден,

           // то возвращаем хранимые данные.

           if (item! = null)

           {

              return item. Value;

           }

       }

 

       // Возвращаем null если ничего найдено.

       return null;

   }

 

   /// < summary>

   /// Хеш функция.

   /// < /summary>

   /// < remarks>

   /// Возвращает длину строки.

   /// < /remarks>

   /// < param name=" value" > Хешируемая строка. < /param>

   /// < returns> Хеш строки. < /returns>

   private int GetHash(string value)

   {

       // Проверяем входные данные на корректность.

       if (string. IsNullOrEmpty(value))

       {

           throw new ArgumentNullException(nameof(value));

       }

 

       if (value. Length > _maxSize)

       {

           throw new ArgumentException($" Максимальная длинна ключа составляет {_maxSize} символов. ", nameof(value));

       }

 

       // Получаем длину строки.

       var hash = value. Length;

       return hash;

   }

}

}

Вызов

using System;

 

namespace HashTable

{

class Program

{

   static void Main(string[] args)

   {

       // Создаем новую хеш таблицу.

       var hashTable = new HashTable();

 

       // Добавляем данные в хеш таблицу в виде пар Ключ-Значение.

       hashTable. Insert(" Little Prince", " I never wished you any sort of harm; but you wanted me to tame you... " );

       hashTable. Insert(" Fox", " And now here is my secret, a very simple secret: It is only with the heart that one can see rightly; what is essential is invisible to the eye. " );

       hashTable. Insert(" Rose", " Well, I must endure the presence of two or three caterpillars if I wish to become acquainted with the butterflies. " );

       hashTable. Insert(" King", " He did not know how the world is simplified for kings. To them, all men are subjects. " );

 

       // Выводим хранимые значения на экран.

       ShowHashTable(hashTable, " Created hashtable. " );

       Console. ReadLine();

 

       // Удаляем элемент из хеш таблицы по ключу

       // и выводим измененную таблицу на экран.

       hashTable. Delete(" King" );

       ShowHashTable(hashTable, " Delete item from hashtable. " );

       Console. ReadLine();

 

       // Получаем хранимое значение из таблицы по ключу.

       Console. WriteLine(" Little Prince say: " );

       var text = hashTable. Search(" Little Prince" );

       Console. WriteLine(text);

       Console. ReadLine();

   }

 

   /// < summary>

   /// Вывод хеш-таблицы на экран.

   /// < /summary>

   /// < param name=" hashTable" > Хеш таблица. < /param>

   /// < param name=" title" > Заголовок перед выводом таблицы. < /param>

   private static void ShowHashTable(HashTable hashTable, string title)

   {

       // Проверяем входные аргументы.

       if(hashTable == null)

       {

           throw new ArgumentNullException(nameof(hashTable));

       }

 

       if(string. IsNullOrEmpty(title))

       {

           throw new ArgumentNullException(nameof(title));

       }

 

       // Выводим все имеющие пары хеш-значение

       Console. WriteLine(title);

       foreach (var item in hashTable. Items)

       {

           // Выводим хеш

           Console. WriteLine(item. Key);

 

           // Выводим все значения хранимые под этим хешем.

           foreach(var value in item. Value)

           {

               Console. WriteLine($" \t{value. Key} - {value. Value}" );

           }

       }

       Console. WriteLine();

   }

}

}

 

На платформе. NET уже есть готовая реализация данной структуры данных. Она содержится в пространстве имен System. Collections и называется аналогично Hashtable.

 

В классе Hashtable реализуются интерфейсы IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback и ICloneable. В классе Hashtable определено немало конструкторов. Ниже приведены наиболее часто используемые конструкторы этого класса:

 

public Hashtable()

public Hashtable(IDictionary d)

public Hashtable(int capacity)

public Hashtable(int capacity, float loadFactor)

В первой форме создается создаваемый по умолчанию объект класса Hashtable. Во второй форме создаваемый объект типа Hashtable инициализируется элементами из коллекции d. В третьей форме создаваемый объект типа Hashtable инициализируется, учитывая емкость коллекции, задаваемую параметром capacity. И в четвертой форме создаваемый объект типа Hashtable инициализируется, учитывая заданную емкость capacity и коэффициент заполнения loadFactor. Коэффициент заполнения, иногда еще называемый коэффициентом загрузки, должен находиться в пределах от 0. 1 до 1. 0. Он определяет степень заполнения хеш-таблицы до увеличения ее размера. В частности, таблица расширяется, если количество элементов оказывается больше емкости таблицы, умноженной на коэффициент заполнения. В тех конструкторах, которые не принимают коэффициент заполнения в качестве параметра, этот коэффициент по умолчанию выбирается равным 1. 0.

 

В классе Hashtable определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов этого класса приведены ниже. В частности, для того чтобы определить, содержится ли ключ в коллекции типа Hashtable, вызывается метод ContainsKey(). А для того чтобы выяснить, хранится ли в такой коллекции конкретное значение, вызывается метод ContainsValue(). Для перечисления содержимого коллекции типа Hashtable служит метод GetEnumerator(), возвращающий объект типа IDictionaryEnumerator. Напомним, что IDictionaryEnumerator — это перечислитель, используемый для перечисления содержимого коллекции, в которой хранятся пары " ключ-значение".

 

ContainsKey()

Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится ключ, а иначе — логическое значение false

 

ContainsValue()

Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится значение, а иначе — логическое значение false

 

GetEnumerator()

Возвращает для вызывающей коллекции типа Hashtable перечислитель типа IDictionaryEnumerator

 

Synchronized()

Возвращает синхронизированный вариант коллекции типа Hashtable, передаваемой в качестве параметра

 

В классе Hashtable доступны также открытые свойства, определенные в тех интерфейсах, которые в нем реализуются. Особая роль принадлежит двум свойствам, Keys и Values, поскольку с их помощью можно получить ключи или значения из коллекции типа Hashtable. Эти свойства определяются в интерфейсе IDictionary следующим образом:

 

public virtual ICollection Keys { get; }

public virtual ICollection Values { get; }

В классе Hashtable не поддерживаются упорядоченные коллекции, и поэтому ключи или значения получаются из коллекции в произвольном порядке. Кроме того, в классе Hashtable имеется защищенное свойство EqualityComparer. А два других свойства, hep и comparer, считаются устаревшими.

 

Давайте расмотрим пример:

 

using System;

using System. Collections;

 

namespace ConsoleApplication1

{

class Program

{

   static void Main()

   {

       // Создаем хеш-таблицу

       Hashtable ht = new Hashtable();

 

       // Добавим несколько записей

       ht. Add(" alex85", " 12345" );

       ht. Add(" fg230404", " 12ldsd" );

       ht. Add(" I_best_user", " 12349" );

 

       // Считаем коллекцию ключей

       ICollection keys = ht. Keys;

 

       foreach (string s in keys)

           Console. WriteLine(s + ": " + ht[s]);

 

       Console. ReadLine();

   }

}

}

 

Класс Hashtable платформы Net

 

В классе Hashtable реализуются интерфейсы IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback и ICloneable. В классе Hashtable определено немало конструкторов. Ниже приведены наиболее часто используемые конструкторы этого класса:

public Hashtable()

public Hashtable(IDictionary d)

public Hashtable(int capacity)

public Hashtable(int capacity, float loadFactor)

 

В первой форме создается создаваемый по умолчанию объект класса Hashtable. Во второй форме создаваемый объект типа Hashtable инициализируется элементами из коллекции d. В третьей форме создаваемый объект типа Hashtable инициализируется, учитывая емкость коллекции, задаваемую параметром capacity. И в четвертой форме создаваемый объект типа Hashtable инициализируется, учитывая заданную емкость capacity и коэффициент заполнения loadFactor. Коэффициент заполнения, иногда еще называемый коэффициентом загрузки, должен находиться в пределах от 0. 1 до 1. 0. Он определяет степень заполнения хеш-таблицы до увеличения ее размера. В частности, таблица расширяется, если количество элементов оказывается больше емкости таблицы, умноженной на коэффициент заполнения. В тех конструкторах, которые не принимают коэффициент заполнения в качестве параметра, этот коэффициент по умолчанию выбирается равным 1. 0.

 

В классе Hashtable определяется ряд собственных методов, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Некоторые из наиболее часто используемых методов этого класса приведены ниже. В частности, для того чтобы определить, содержится ли ключ в коллекции типа Hashtable, вызывается метод ContainsKey(). А для того чтобы выяснить, хранится ли в такой коллекции конкретное значение, вызывается метод ContainsValue(). Для перечисления содержимого коллекции типа Hashtable служит метод GetEnumerator(), возвращающий объект типа IDictionaryEnumerator. Напомним, что IDictionaryEnumerator — это перечислитель, используемый для перечисления содержимого коллекции, в которой хранятся пары " ключ-значение".

ContainsKey()

Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится ключ, а иначе — логическое значение false

ContainsValue()

Возвращает логическое значение true, если в вызывающей коллекции типа Hashtable содержится значение, а иначе — логическое значение false

GetEnumerator()

Возвращает для вызывающей коллекции типа Hashtable перечислитель типа IDictionaryEnumerator

Synchronized()

Возвращает синхронизированный вариант коллекции типа Hashtable, передаваемой в качестве параметра

 

В классе Hashtable доступны также открытые свойства, определенные в тех интерфейсах, которые в нем реализуются. Особая роль принадлежит двум свойствам, Keys и Values, поскольку с их помощью можно получить ключи или значения из коллекции типа Hashtable. Эти свойства определяются в интерфейсе IDictionary следующим образом:

public virtual ICollection Keys { get; }

public virtual ICollection Values { get; }

 

В классе Hashtable не поддерживаются упорядоченные коллекции, и поэтому ключи или значения получаются из коллекции в произвольном порядке. Кроме того, в классе Hashtable имеется защищенное свойство EqualityComparer. А два других свойства, hep и comparer, считаются устаревшими.

 

Давайте расмотрим пример:

using System;

using System. Collections;

 

namespace ConsoleApplication1

{

class Program

{

   static void Main()

   {

       // Создаем хеш-таблицу

       Hashtable ht = new Hashtable();

 

       // Добавим несколько записей

       ht. Add(" alex85", " 12345" );

         ht. Add(" fg230404", " 12ldsd" );

       ht. Add(" I_best_user", " 12349" );

 

       // Считаем коллекцию ключей

       ICollection keys = ht. Keys;

 

       foreach (string s in keys)

           Console. WriteLine(s + ": " + ht[s]);

 

       Console. ReadLine();

   }

}

}

Добавьте в пример операцию удаления записей.

 

Контрольные вопросы:

1. Понятие хеш-таблицы.

2. В чем сходство и различие структуры хеш-таблицы и словаря в программировании?

3. Область применения хеш-таблиц в системном программировании? В прикладном? При изучении каких тем вы встречали пары ключ-значение?

4. Как вы понимаете термин «хеширование»?

5. Какие способы реализации хеш-таблиц вам известны?

6. Какой класс(свойства, методы) платформы Net реализует хеш-таблицы?

 

Литература:

Таненбаум Э., Бос Х. Т18 Современные операционные системы. 4-е изд. — СПб.: Питер, 2015. — 1120 с.: ил. — (Серия «Классика computer science»). ISBN 978-5-496-01395-6

Интернет источники:

 

https: //ru. wikipedia. org/wiki/%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0

 

https: //professorweb. ru/my/csharp/charp_theory/level12/12_5. php

 

https: //shwanoff. ru/hashtable/

 

https: //www. youtube. com/watch? v=PwAeMJGjKk8 начиная с 03: 50



  

© helpiks.su При использовании или копировании материалов прямая ссылка на сайт обязательна.