Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_5801.html
 

Контейнеры "словари" и замена массивов на записи.

Опубликовано 21.01.2013 г. 18:25

Все, конечно же, знают, что существует достаточно большое количество различных структур для хранения данных. Самыми простыми,  и наиболее часто используемыми являются, наверное, списки и словари. Далее уже пойдут очереди, стеки и т.п. Список предоставляет нам способ получить доступ к элементу, зная его порядковый номер. В качестве структур-списков мы можем рассматривать не только классы вроде TList и их наследников, а также  их аналоги, но и простые массивы.
В случае со словарями доступ к элементу осуществляет не по порядковому номеру, а по значению ключа. Ключом в принципе может выступать любой тип данных, главное, чтобы их можно было сравнивать. Достаточно часто на практике используются словари с целочисленными ключами. И опять в качестве словаря мы можем рассматривать не только аналоги и потомки TDictionary, но и массивы. С этой точки зрения язык очень гибок и позволяет нам определять массивы, например, следующим образом:

type
    TKeySet  = (ksOne, ksTwo, ksThree);
    TMyArray = array[TKeySet] of integer;

var
    values : TMyArray;

В моей же работе, часто возникает необходимость обрабатывать данные, которые представлены по годам. Когда я начал работать над проектом (вернее сказать, продолжил работу по разработке), то для представления данных использовались обычные массивы вида array[1..30] of integer; Далее подразумевалось, что индекс номер 1 соответствует, например, 1990 году, и все это сводилось к куче говно-кода для вычисления индексов, и неимоверному количеству ошибок. После весьма продолжительного рефакторинга таких мест стало намного меньше, и поскольку диапазоны годов были весьма ограничены в некоторых местах подобные массивы были заменены на следующие:

const
    START_YEAR = 2000;
    END_YEAR   = 2020;
type
    TYearArray = array[START_YEAR .. END_YEAR] of integer;

 

 Такое решение не теряет своей эффективности по сравнению с обычными массивами, но зато полностью устраняет проблему необходимости вычисления различных индексов для поиска нужных данных. Вероятность возникновения ошибок многократно снижается, код становится проще, и намного лучше читается, и проще воспринимается. В моей работе часто такие массивы использовались для храения, например, какой то статистической информации. И константа END_YEAR обычно указывала на последний год фактических данных. Но время на месте не стоит, и периодически возникает необходимость изменять ее значение.

Одной из проблем, над которой я работал посление несколько дней, был как раз-таки рефакторинг кода с той целью, чтобы изменить тип данных таким образом, что начальный год был известен, а вот последний диктовался системой. При этом необходимо учесть пару факторов. Во первых, просто заменить подобные массивы на словари TDictionary, наверное, не самая лучшая идея с точки зрения эффективности (особенно когда диапазон всего на 2 года, к примеру). Во вторых, реализация должна быть такой, чтобы минимизировать модификацию вызывающего кода. Так для установки значения в словаре TDictionary (первое использование ключа), нам потребуется вызов методов Add или AddOrSetValue, что идет в разрез с обычным присванием values[2012] := 0, в вызывающем коде. В добавок ко всему, экземляр словаря необходимо предварительно создать, а затем разрушить (На эту тему, кстати говоря, вспомнилась забавная глава из книги Delphi 2010 HandBook от Марко Канту, под названием Smart Pointers).

Помимо классов/объектов мы имеем структуры/записи. В нашем случае структура нам подходит с той точки зрения, что ее не надо создавать.

type
    TArrayData = record
      strict private
        FValues : array of integer;
        FInitialized : boolean;

        procedure Initialize();

        function getValue(year : integer): integer; inline;
        procedure setValue(year: integer; value : integer); inline;
      public
        const StartYear = 2010;
        property Values[year : integer] : integer read getValue write setValue; default;
    end;

implementation

procedure TArrayData.Initialize();
begin
    SetLength(FValues, GlobalSystemYear - startYear);
    FInitialized := true;
end;

function TArrayData.getValue(year: integer): integer;
begin
    result := FValues[year - StartYear];
end;

procedure TArrayData.setValue(year, value: integer);
begin
    if not FInitialized then
        Initialize();

    FValues[year - StartYear] := value;
end;

В результате использования такого контейнера, доступ к элементам остается тем же, каким был при использовании обычных массивов, а именно values[2012] := 1; что достигается с использованием ключевого слова default. Получаем весьма легковесный (по сравнению с TDictionary) контейнер для хранения данных c простой реализацией. Конечно, доступ к элментам в таком случае не такой эффективный как при обычных массивах, но это в данном случае не есть цель. В  некоторых случаях код Initialize мы можем вынести в конструктор записи (вернее заменить на конструктор, но его придется вызывать вручную).

При необходимости к структуре можно добавить типизацию от параметра T, и использовать контейнер для произвольных тпиов значений, но это уже, как говорится, другая история. Помимо этого, записи позволяют перегружать операторы, что также может быть нам полезным в какой то степени. Вдовесок ко всему мы можем реализовать собственный итератор для подобной структуры и использьвать циклы вида for .. in.

Метки:  ООП  |  delphi 

Комментарии

Darked
21.01.2013 в 21:55
"...что достигается с использованием ключевого слова default."

Что-то не вижу я этого ключевого слова в привед]нном коде.
Desmos
21.01.2013 в 21:48
Попробуй горизонтальную прокрутку в контейнере с кодом)
Darked
21.01.2013 в 22:08
Виноват, браузер косячит, нет прокрутки, в другом все ок, спасибо.
teran
21.01.2013 в 22:00
:)
Can you get taller with yoga?
01.08.2017 в 07:56
Heya i am for the first time here. I found this board and I find It truly useful &
it helped me out much. I hope to give something back and aid others
like you helped me.
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно