Motto

В тихом саду здравомыслия
Пусть на вас постоянно падают
кокосовые орехи пробужденности.
Чогьям Трунгпа РИНПОЧЕ


Версия для мобильного


вторник, 14 июля 2009 г.

2. Обобщённое программирование(generics) в Delphi 2009 для Win32. Часть 2. Использование.

В пособии также рассматриваются анонимные методы и процедурные ссылки.



Словарь терминов

  • generics - дженерики, генерики, параметризованные классы, шаблоны, обобщения. Мне кажется, что наиболее подходящим для перевода словом будет “обобщения”, но в народе их чаще называют: дженерики;
  • generic class – обобщённый класс;
  • сast – приведение типа;
  • anonymous routines - анонимные методы;
  • routine references - процедурные ссылки;
  • ordinal type – порядковый тип (данных);
  • interface type – интерфейсный тип (данных);
  • comparer – компаратор

II. Повседневное использование на примере TList<T>

Парадоксально, но мы начнем с примера повседневного использования обобщённых классов (более корректно говорить: обобщённый шаблон класса), вместо дизайна такого типа. Есть несколько веских причин для этого.

Во-первых, значительно легче написать класс, когда у вас есть достаточно четкое представление о том, каким образом вы собираетесь его использовать. Это еще более очевидно в случаях, когда вы открываете для себя новые парадигмы программирования.

Во-вторых, большинство презентаций объектно-ориентированного программирования, как правило, сначала объясняют, как использовать имеющиеся классы.

II-A. Простой код, для начала

Давайте постепенно начнем. Давайте напишем небольшую программу вычисляющую квадраты целых чисел от 0 до X. X будет задаваться пользователем.

Без дженериков, мы бы могли использовать динамический массив (и это было бы гораздо лучше, просто помните о том, что это учебный пример), но мы будем использовать список целочисленных чисел (integer).

Вот код:

program TutoGeneriques;
  

{$APPTYPE CONSOLE}
  

uses
   SysUtils, Classes, Generics.Collections;
  

procedure WriteSquares(Max: Integer);

var
   List: TList<Integer>;
   I: Integer;

begin
   List := TList<Integer>.Create;
   try
     for I := 0 to Max do
       List.Add(I*I);
  
     for I := 0 to List.Count-1 do
       WriteLn(Format('%d*%0:d = %d', [I, List[I]]));
   finally
     List.Free;
   end;

end;
  

var
   Max: Integer;

begin
   try
     WriteLn('Please enter a natural number:');
     ReadLn(Max);
     WriteSquares(Max);
   except
     on E:Exception do
       Writeln(E.Classname, ': ', E.Message);
   end;
  
   ReadLn;

end.
          

На что здесь стоит обратить внимание?

Прежде всего, конечно, это объявление переменной List, наряду с кодом созданием экземпляра. Мы взяли название обобщённого класса TList, и добавили к нему фактический параметр (тип данных, а не значение) между угловыми скобками < и >.

var
   List: TList<Integer>;

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

Некоторые языки позволяют использовать выведение типа (примечание переводчика: type inference; также смотрите комментарий от Alex Neznanov), позволяя компилятору угадывать тип параметра. Но в Delphi Win32 такой фокус не пройдёт. Поэтому нельзя писать так:

var
  List: TList<Integer>;
  begin
   List := TList.Create; // здесь не хватает <Integer>
   ...
  end;

Во-вторых, что более важно, но менее заметно, поскольку речь идёт о том, что фактически отсутствует в коде. А именно cast. Дело в том, что нет необходимости приводить тип Integer к типу Pointer! Удобно, не правда ли? Кроме того, такой код легче читать.

Ещё одно преимущество – это более высокая безопасность при приведении типов при использовании дженериков. Используя приведения типов, всегда остаётся риск совершить ошибку, например, добавив Pointer в список, предназначенный для хранения integer, или наоборот. Если правильно использовать дженерики, то Вам, скорее всего не придётся прибегать к приведению типов вообще, или всего пару раз. Таким образом, шанс сделать ошибку уменьшается. Более того, даже если попытаться добавить Pointer к объекту класса TList<Integer>, компилятор откажется компилировать такой код. Без дженериков, такую ошибку можно было бы выявить только по абсурдному значению, может быть даже спустя месяцы после релиза программы.

Я использовал в примере тип Integer, чтобы ограничить код, не связанный непосредственно с представлением дженериков, но на деле, вместо Integer можно использовать любой тип данных.

II-B. Присвоения между обобщёнными классами

Когда речь заходит о наследовании, как вы знаете, мы можем присвоить экземпляр наследника переменной родительского класса, но не наоборот. Как с этим обстоит дело у дженериков?

Начнём с понимания того принципа, что с дженериками мы не можем сделать ничего подобного! Код в следующем примере являются недействительным и не скомпилируется:

var
   NormalList: TList;
   IntList: TList<Integer>;
   ObjectList: TList<TObject>;
   ComponentList: TList<TComponent>;

begin
   ObjectList := IntList;
   ObjectList := NormalList;
   NormalList := ObjectList;
   ComponentList := ObjectList;
   ObjectList := ComponentList; // да, даже это не будет работать
end;

Как указано в комментарии, несмотря на тот факт, что TComponent являются наследниками от TObject, переменная типа TList<TComponent> не может быть присвоена переменной типа TList<TObject>. Для того чтобы понять почему так происходит, представьте, что если бы присвоение работало, то TList<TObject>.Add(Value: TObject) позволило бы добавлять значение TObject в список, состоящий из типов TComponent!

Другая важная вещь, на которую стоит обратить внимание, что TList<T> не является ни особым случаем, ни обобщением от TList. На самом деле, это совершенно разные типы — первый объявлен в модуле Generics.Collections, а последний в модуле Classes!

II-C. Методы TList<T>

Примечательно, что TList<T> не предоставляет тот же набор методов, как TList. В нашем распоряжении есть больше методов "высокого уровня", но менее "низкоуровневых " методов (например, таких, как Last, который отсутствует).

Новые методы:

  • 3 перегруженные версии методов AddRange, InsertRange и DeleteRange: они эквивалентны методам Add/Insert/Delete соответственно, но работают со списками элементов:
  • Метод Contains;
  • Метод LastIndexOf;
  • Метод Reverse;
  • 2 перегруженные версии Sort (это название не является новым, но способ использования отличается);
  • 2 перегруженные версии метода BinarySearch.

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

В самом деле, в чём сегодня смысл добавления метода AddRange к устаревшему классу TList? Никакого. Потому что каждый элемент должен подвергаться преобразованию типа. Поэтому, вам всё равно нужно было бы писать цикл, формирующий массив (с преобразованием типов на каждой итерации), для передачи его (массива) в AddRange. Вместо этого гораздо проще просто вызывать Add в каждой итерации.

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

Основная мысль этой главы в том, что дженерики дают огромную свободу при повторном использовании кода.

II-D. TList<T> и компараторы

Конечно, TList<T> может работать с любым типом данных. Но вот чего он не знает, это того, каким образом нужно сравнивать два элемента? Как узнать, что они равны, чтобы заработал поиск с помощью IndexOf? Как узнать, какой элемент меньше, чем другой, с чтобы упорядочить список?

Ответ компараторы. Компаратор является интерфейсом типа IComparer<T>.  Этот тип объявлен в Generics.Defaults.pas.

При создании экземпляра TList<T>, вы можете передать компаратор в качестве параметра в конструктор, и все методы, которые в нём нуждаются, будут его использовать. В противном случае будет использоваться компаратор по умолчанию.

Используемый по умолчанию компаратор будет зависеть от типа элемента. Чтобы получить его, TList<T> вызывает метод класса TComparer<T>. Default. Этот метод с помощью грязных манипуляций с RTTI, пытается получить наиболее подходящее решение, которое, однако, не всегда является подходящим.

Стандартный компаратор можно использовать для следующих типов данных:

  • Порядковые (ordinal) типы (целые числа, символы, Булевый тип, перечисления);
  • Типы с плавающей запятой;
  • Множества (для проверки на равенство);
  • Длинные юникодные строки (string, UnicodeString и WideString);
  • Длинные ANSI строки (AnsiString), но без учёта кодовой страницы;
  • Типы variant (только для проверки на равенство);
  • Классы (только для проверки на равенство: используется TObject.Equals метод);
  • Указатели, мета-классы и интерфейсные типы (только для проверки на равенство);
  • Статические и динамические массивы, состоящие из ordinal типов, чисел с плавающей запятой или множеств (только проверка на равенство).

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

Для этого существуют два простых способа. Первый основан на написание функции, другой на наследовании класса TComparer<T>. Мы проиллюстрируем оба, на примере реализации сравнения для точки (TPoint). Предположим, что точки сравниваются согласно их удалённости точки-источника (0, 0), для того иметь возможность провести линейное упорядочивание (в математическом смысле).

II-D-1. Пишем компаратор с использованием TComparer<T>

Это очень просто! Всё что нужно сделать – это переопределить метод: Compare. Он должен вернуть:

  1. 0 в случае равенства,
  2. положительное целое число, если первый параметр больше, чем второй,
  3. отрицательное целое если первый параметр меньше второго.

Вот результат:

function DistanceToCenterSquare(const Point: TPoint): Integer; inline;
begin
  Result := Point.X*Point.X + Point.Y*Point.Y;
end;  

type
  TPointComparer = class(TComparer<TPoint>)
    function Compare(const Left, Right: TPoint): Integer; override;
  end;


function TPointComparer.Compare(const Left, Right: TPoint): Integer;
begin
  Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right);
end;    

информация

Обратите внимание на родителя класса TPointComparer: он наследуется от TComparer<TPoint>. Как вы видите, вполне возможно наследовать обычный класс от обобщённого класса.

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

Вот небольшая программа, которая создает 10 случайных точек, упорядочивает и выводит упорядоченный список:

function DistanceToCenter(const Point: TPoint): Extended; inline;
begin
   Result := Sqrt(DistanceToCenterSquare(Point));
end;
  
procedure SortPointsWithTPointComparer;
const
  MaxX = 100;
  MaxY = 100;
  PointCount = 10;
var
  List: TList<TPoint>;
  I: Integer;
  Item: TPoint;
begin
  List := TList<TPoint>.Create(TPointComparer.Create);
  try
    for I := 0 to PointCount-1 do
      List.Add(Point(Random(2*MaxX+1) - MaxX, Random(2*MaxY+1) - MaxY));
  
    List.Sort; // там используется компаратор, который был передан в конструктор
  
    for Item in List do
      WriteLn(Format('%d'#9'%d'#9'(distance to origin = %.2f)',
        [Item.X, Item.Y, DistanceToCenter(Item)]));
  finally
    List.Free;
  end;
end;
  
begin
  try
    Randomize;
  
    SortPointsWithTPointComparer;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.   
Внимание! Важное сообщение!

Подождите минуту! А где же освобождается наш компаратор? Напомним, что TList<T> принимает интерфейс от типа IComparer<T>, а не класс. Благодаря механизму подсчёта ссылок для интерфейсов, который корректно реализован в TComparer<T>, компаратор будет автоматически освобождён, когда он перестанет быть нужным. Если вы не знаете ничего об интерфейсах в Delphi, я советую прочитать статью Программирование на языке Delphi: Глава 6. Интерфейсы (на русском языке).

II-D-2. Пишем компаратор, используя функцию сравнения

Этот вариант кажется более простым, поскольку как понятно из названия, нет необходимости возиться с дополнительными классами. Тем не менее, я решил представить его вторым, потому он знакомит с новым типом данных, появившимся в Delphi 2009 – процедурными ссылками (routine references).

Пытливый читатель сейчас, наверное, захочет возразить, что он уже давно знаком с процедурными ссылками. Хммм… навряд ли. ;-) Скорее всего, читатель подразумевает сейчас процедурные типы (procedural types), например, TNotifyEvent.

type
  TNotifyEvent = procedure(Sender: TObject) of object;

Процедурные ссылки же объявляются по-другому. Вот пример для TComparison<T>:

type
  TComparison<T> = reference to function(const Left, Right: T): Integer;

Между процедурными ссылками и процедурными типами есть как минимум три отличия:

Во-первых, процедурные ссылки не могут быть методами объекта (помечены as object). Иными словами, вы никогда не сможете присвоить процедурной ссылке метод объекта, а только процедуры или функции. (По крайней мере, мне так и не удалось добиться успеха, пытаясь это сделать ^ ^.)

Второе отличие более фундаментально: в то время как процедурные типы (не методы объекта) является указателем на базовый адрес процедуры (точку входа процедуры), процедурные ссылки, по сути, являются интерфейсом! Со всеми делами, вроде подсчета ссылок и всех остальных штук. Однако, Вас это, вероятно, не будет заботить, поскольку повседневное использование не отличается от использования процедурных типов.

И наконец (и это объяснит появление процедурных ссылок), существует возможность назначить анонимную процедуру (мы сейчас посмотрим, на что это похоже) для процедурной ссылки, но не для процедурного типа. Если Вы попробуете так сделать, то получите сообщение об ошибке от компилятора. Кстати, это также объясняет, почему процедурные ссылки реализованы как интерфейсы, но упоминание об этом не входит в рамки этого руководства. (Примечание переводчика: в оригинале было «Incidentally, that explains also why routine references are implemented as interfaces, but thoughts on this subject are not within the framework of this tutorial.»)

Давайте же вернемся к нашей сортировке точек. Чтобы создать компаратор на основе функции, мы используем другой метод класса TComparer<T>, - классовую функцию Construct. Этот классовый метод принимает в качестве параметра ссылку на функцию типа TComparison<T>. Как было уже сказано, использование процедурных ссылок очень похоже на использование процедурных типов: в качестве параметра можно передавать название процедуры. Вот код:

function ComparePoints(const Left, Right: TPoint): Integer;
begin
   Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right);
end;  

procedure SortPointsWithComparePoints;
const
   MaxX = 100;
   MaxY = 100;
   PointCount = 10;
var
   List: TList<TPoint>;
   I: Integer;
   Item: TPoint;
begin
  List := TList<TPoint>.Create(TComparer<TPoint>.Construct(ComparePoints));
  try
    for I := 0 to PointCount-1 do
      List.Add(Point(Random(2*MaxX+1) - MaxX, Random(2*MaxY+1) - MaxY));
  
    List.Sort; // используется компаратор переданный в конструктор
  
    for Item in List do
      WriteLn(Format('%d'#9'%d'#9'(distance to origin = %.2f)',
        [Item.X, Item.Y, DistanceToCenter(Item)]));
  finally
    List.Free;
  end;
end;


begin
  try
    Randomize;

    SortPointsWithComparePoints;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;

  ReadLn;
end.

Единственное место, которое изменилось – это создание компаратора. Остальной код использования списка, в целом, остался таким, как и раньше.

Внутри метода Construct создается экземпляр TDelegatedComparer<T>, который принимает в качестве параметра своего конструктора ссылку на процедуру (процедурную ссылку), которая будет отвечать за сравнение. Таким образом, вызов Construct вернёт объект того же типа, который инкапсулирован в интерфейсе IComparer<T>. (примечание переводчика: в оригинале было «Calling Construct thus returns an object of this type, encapsulated in an interface IComparer<T>.»)

Ну, это тоже очень просто. По сути, мы должны помнить о том, что обобщения (дженерики) предназначены для того, чтобы сделать жизнь легче!

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

procedure SortPointsWithAnonymous;
var
   List: TList<TPoint>;
   // ...
begin
   List := TList<TPoint>.Create(TComparer<TPoint>.Construct(
     function(const Left, Right: TPoint): Integer
     begin
       Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right);
     end));

   // Always the same ...
end;

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

информация

К слову об анонимных процедурах: да, они имеют доступ к локальным переменным той процедуры/метода, в котором они объявлены (примечание переводчика: в оригинале использовалось слово enclosing - заключены; см. также комментарий). И да, они могут делать это даже после того, как процедура или метод, в которой они были объявлены (примечание переводчика: здесь тоже было enclosing), закончил выполнение. Следующий пример проиллюстрирует это:

function MakeComparer(Reverse: Boolean = False): TComparison<TPoint>;
begin
  Result :=
    function(const Left, Right: TPoint): Integer
    begin
      Result := DistanceToCenterSquare(Left) - DistanceToCenterSquare(Right);
      if Reverse then
        Result := -Result;
    end;
end; 

procedure SortPointsWithAnonymous;
var
  List: TList<TPoint>;
  // ...
begin
  List := TList<TPoint>.Create(TComparer<TPoint>.Construct(
    MakeComparer(True)));

   // Always the same ...
end; 

Интересно, не так ли?

Вуаля! Вот и подошёл к концу наш небольшой тур по использованию компараторов с TList<T>, а вместе с ним, и введение в генерики на примере использования этого класса. В следующей главе мы начнем изучать, как можно написать свой собственный обобщённый класс.



21 комментарий:

  1. Непереведённый блок я бы перевёл так:

    В самом деле, в чём сегодня смысл добавления метода AddRange к устаревшему классу TList? Никакого. Потому что каждый элемент должен подвергаться преобразованию типа. Поэтому, вам всё равно нужно было бы писать цикл, формирующий массив (с преобразованием типов на каждой итерации), для передачи его (массива) в AddRange. Вместо этого гораздо проще просто вызывать Add в каждой итерации.

    ОтветитьУдалить
  2. GunSmoker, спасибо большое! =)) А то я замучался ломать себе моск, пытаясь понять о чём был тот блок.

    ОтветитьУдалить
  3. Спасибо за статью !

    А самое "массивное" применение новых возможностей Delphi 2009 я видел только в DeHL (http://code.google.com/p/delphilhlplib/).

    IMHO, лучшая библиотека контейнеров, хотя в ней есть и "большие" числа, и своя реализация работы с датой/временем и многое другое.

    ОтветитьУдалить
  4. Спасибо за комментарий, Дмитрий. =)

    ОтветитьУдалить
  5. Спасибо за очень полезное дело.
    Захотелось сделать несколько терминологических замечаний.

    1. type inference - это "выведение типа" или "вывод типа", причём это термин. Наверное не стоило так сильно искажать смысл.
    К тому же "тип данных параметра" скорее всего тоже неправильно, так как сам параметр обобщённого класса и является типом данных.

    Может, правильнее было бы сказать:
    Некоторые языки, поддерживающие выведение типов, позволяют повторно не указывать параметры однажды объявленного обобщённого класса, перекладывая обязанность определения точного типа данных на компилятор.

    2. "Метод объекта" - это масло маслянное, вполне можно при противопоставлении метода и обычной подпрограммы слово "объект" не указывать.
    При этом словосочетание "процедурные типы (не методы объекта)" не верно совсем, так как метод - это вообще не тип. А прибавление "of object" не делает из процедурного типа что-то иное, это всё равно процедурный тип, хотя можно ввести свой термин, например "методный тип" (сходу крсивое название не придумал :).

    3. Про "enclosed". В русском языке вроде бы устоялось "в контексте ... (чего-то там) они объявлены". Ещё лучше всего использовать академическое "область видимости", но это может затруднить чтение.

    4. Хочется сначала посмотреть следующие части, но замечу, что объяснить, как работают замыкания (closures, те самые анонимные процедуры - их частный случай), необходимо, иначе можно наломать таких дров по прочтении...
    Или предостеречь об бездумного использования ссылок на подпрограммы.

    P.S. Мне нравится следовать Вирту, который чётко разделял подпрограммы на процедуры и функции, что нашло отражение в том числе и в Delphi, хотя это конечно не Fortran. Routine, procedure и function можно употреблять точнее.

    ОтветитьУдалить
  6. Alex Neznanov, большое спасибо за дельные замечания! А то терминология - не самая сильная моя сторона. [зачёркнуто]Она у меня вроде правильная, но хромает, как у Винни Пуха с правильнописанием.[/зачёркнуто] =)

    ОтветитьУдалить
  7. Подправил пост в соответствии с замечанием по пункту 1. По пунктам 2 и 3 добавил ссылки на комментарий, но текст не менял. Моя цель - понятный перевод, а не соблюдение академической точности при передаче изложенной в статье информации.
    Более того, все мои знания о генериках тупо почерпнуты из этой самой статьи. Поэтому, по пункту 4, я ничего сверх того что есть в оригинале, добавить не могу.
    Я веду к том, что если у Вас будут более детальные замечания о дровах, то напишите о них, и я с удовольствием упомяну о них в переводе. Но сам объяснять замыкания(closures) не буду, ибо и в теме не разбираюсь, и сочинять не хочу. =)

    ОтветитьУдалить
  8. Поищи по тексту "с чтобы", буква с лишняя. Что это за редактор голимый. Текст нельзя вставить. Перемещение стрелками не работает. Ужас.

    ОтветитьУдалить
  9. Поищи по тексту "является указателем", правильно было бы "являются указателями". Так наверное :)

    ОтветитьУдалить
  10. Чё-то я немного недопонимаю дженерики. Почему вот такой код не компилируется:
    type
    TPostStruct = record
    id: Ansistring;
    title: Ansistring;
    url: Ansistring;
    end;
    ...
    type
    TPostList = TList;
    ...
    var FPostList:TPostList;
    S:TPostStruct;
    begin
    FPostList:=TPostList.Create;
    FPostList.Add(S);
    FPostList[0].id:='para-ram';
    end;
    Ошибка: [DCC Error] ...: E2064 Left side cannot be assigned to
    А если написать так:
    type
    TPostStruct = class
    id: Ansistring;
    title: Ansistring;
    url: Ansistring;
    end;
    ...
    type
    TPostList = TList;
    ...
    var FPostList:TPostList;
    S:TPostStruct;
    begin
    FPostList:=TPostList.Create;
    FPostList.Add(S);
    FPostList[0].id:='para-ram';
    end;
    То всё компилируется и работает....Это какая-то особенность? С дженериками до сего момента особых навыков работы не имел

    ОтветитьУдалить
  11. Блин...всё чё к generic'у относилось пропало...

    ОтветитьУдалить
  12. Vlad, интересный вопрос. =)
    Но я не знаю почему так.

    Зато такой вариант будет работать:
    FPostList:=TPostList.Create;
    S.id := 'asd';
    FPostList.Add(S);
    S:=FPostList[0];
    FPostList.Add(S);
    s.id := 'param pam';
    FPostList[0] := S;

    ОтветитьУдалить
  13. Как бы...где тут смайлик с большими глазами на выкат? :) Блин, тяжко в последнее время дается самообучение...Спасибо, Алексей. Буду дальше грузть гранит Generics.Collection :)

    ОтветитьУдалить
  14. Да не за что, на самом деле.
    А вот, если в качестве обобщённого параметра вместо записи использовать указатель на запись.

    PPostStruct = ^TPostStruct;
    TPostList = TList<PPostStruct>;

    то и твой код нормально скомпилируется:
    FPostList[0].Id = 'trararam';

    Наверное, дело в указателях.

    ОтветитьУдалить
  15. Кто-то у меня в блоге (по-моему Sergey) писал как раз, что каждый экземпляр класса - это по сути указатель...может быть та и прав, Алексей - в указателях походу собак зарыт...или в глюках :)

    ОтветитьУдалить
  16. Спасибо, очень интересный материал.
    Но вот в TList нету метода вставки в отсортированный список - это недочёт.
    Вот как в TStringList свойство Sorted ставится в тру, и новые строки вставляются "в свое место" в алфавитном порядке
    Не делать же так:
    list.Add(value);
    list.Sort;

    ОтветитьУдалить
  17. =)
    Анонимный, ну можно же написать своего наследника, например TMySortedList, и реализовать там нужное поведение.
    А ещё можно взять готовый чужой класс. В бесплатной библиотеке обобщённых типов DeHL, есть уже реализованный обобщённый класс TSortedList.

    ОтветитьУдалить
  18. Проблему решил стандартными способами =)
    var
    list : TList;
    I: Integer; temp : Integer;
    index : Integer;
    begin
    list := TList.Create;
    for I := 0 to 1000 - 1 do
    begin
    temp := Random(100);
    list.BinarySearch(temp, index);
    list.Insert(Index, temp);
    end;
    for I in List do
    Memo1.Lines.Add(IntToStr(i));
    end;
    Выводится отсортированный список без вызова Sort
    Фишка в том что выходной параметр в методе BinarySearch не отличен от нуля даже если не найден такой элемент.
    Это стало приятным сюрпризом

    ОтветитьУдалить
  19. Скрипт съел дженерики

    ОтветитьУдалить
  20. > Скрипт съел дженерики

    Есть такое дело. Вместо левой угловой скобочки в комментах Блоггера нужно использовать Html Entitiy &_l_t (без подчёркиваний)

    ОтветитьУдалить
  21. К сожалению, TList&ltT> работает очень медленно по сравнению с обычными динамическими массивами.
    Провел тест в профайлере.
    Объявил запись из одной строки и двух целых чисел.
    Прогон по TList из 10000 элементов с поиском максимального значения занял 1.76 миллисекунд. По обычному массиву - 68 микросекунд, т.е. примерно в ~26 раз быстрее.

    ОтветитьУдалить

Постоянные читатели