Как найти индекс записи в TList?

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

Я предполагаю, что могу использовать TList с парами значений (productCode и count), чтобы сделать что-то вроде этого (я пытаюсь рассматривать это как List‹> из С# и т. д.)

procedure myProc
type 
  TProductCodeCount = record
     productCode : string;
     count : integer;
  end;
var
  productCodes : TList<TProductCodeCount>;
  newProductCodeCount : TProductCodeCount;
  currProductCodeCount : TProductCodeCount;
  currentProductCode : string;
  productCodeIndex : integer;
begin
  productCodes :=  TList<TProductCodeCount>.Create;

  // Get my data set
  // Loop through my data set
    begin
      currentProductCode := // value from the database ;
      productCodeIndex := productCodes.indexOf(currentProductCode);
      if productCodeIndex = -1 then
      begin
        newProductCodeCount.productCode := currentProductCode;
        newProductCodeCount.count := 1;

        productCodes.Add(newProductCodeCount)
    end
    else
    begin
      currProductCodeCount := productCodes.Extract(productCodeIndex);
      currProductCodeCount.count := currProductCodeCount.count + 1'
      productCodes.Delete(productCodeIndex);
      productCodes.Add (currProductCodeCount);
    end
  end;

  // Loop through my TList
  begin
     // do something with each productCode and count.
  end
end;

Здесь есть две большие ошибки.

  1. Я не уверен, как закодировать сравнение для работы indexOf (если это вообще возможно для TList типа записи
  2. Обновление элемента списка неуклюже.

Как я могу сделать сравнение? Или есть гораздо лучший способ сделать это?


person BIBD    schedule 16.03.2015    source источник
comment
Почему список пар значений? Если у вас XE7, вы можете использовать TDictionary‹string, Integer›. Затем сравнение можно выполнить, передав результат TComparer‹string›.Construct(...здесь идет код сравнения...) в словарь во время создания.   -  person Rudy Velthuis    schedule 16.03.2015
comment
Я не очень хорошо разбираюсь в базах данных, но я ожидаю, что что-то подобное можно сделать на уровне базы данных. Если поле productCode имеет значение Indexed, то в базе данных уже есть все различные записи, хранящиеся в ее индексной таблице. Как их восстановить я не знаю. Также я почти уверен, что большинство баз данных позволяют вам получить количество записей, соответствующих конкретному запросу, без фактического их извлечения. Поэтому все, что вам нужно, это сделать определенный запрос для каждого конкретного productCode, чтобы увидеть, сколько записей содержит этот конкретный productCode.   -  person SilverWarior    schedule 16.03.2015
comment
Не могли бы вы просто запустить SELECT ProductCode, COUNT(ProductCode) FROM Products GROUP BY ProductCode?   -  person Rob Kennedy    schedule 16.03.2015
comment
@Rudy, использующий словарь, - неплохой способ, в этом случае вам не нужно добавлять какой-либо компаратор.   -  person Dalija Prasnikar    schedule 16.03.2015
comment
@SilverWarior и RobKennedy - вы правы, я мог бы выполнить запрос к БД, а затем просмотреть набор результатов. Я как бы придумал свой пример здесь. На самом деле я изменяю какой-то существующий код, и мне был передан TOraQuery, который я не хочу изменять, и они уже обходят набор результатов по другой причине.   -  person BIBD    schedule 16.03.2015
comment
@RudyVelthuis, TDictionary имеет большой смысл и должен сделать его намного чище.   -  person BIBD    schedule 16.03.2015
comment
@DalijaPrasnikar: В этом случае, я думаю, подойдет компаратор по умолчанию. Но только если достаточно простого сравнения строк ProductCode.   -  person Rudy Velthuis    schedule 16.03.2015
comment
n.b., всем спасибо. Я вернулся и использовал метод TList (с настраиваемым компаратором), так как стало намного удобнее выполнять некоторые другие действия, такие как сортировка и просмотр списка.   -  person BIBD    schedule 17.03.2015


Ответы (1)


Вы можете использовать собственный компаратор, который вы передадите TList<T> во время построения.

type
  TProductCodeCountComparer = class(TComparer<TProductCodeCount>)
  public
    function Compare(const Left, Right: TProductCodeCount): Integer; override;
  end;

function TProductCodeCountComparer.Compare(const Left, Right: TProductCodeCount): Integer; 
begin
  Result := CompareStr(Left.productCode, Right.productCode);
end;

var
  Comparer: IComparer<TProductCodeCount>

Comparer := TProductCodeCountComparer.Create;
productCodes :=  TList<TProductCodeCount>.Create(Comparer);

Вы также можете создать компаратор на месте

  productCodes := TList<TProductCodeCount>.Create(
    TComparer<TProductCodeCount>.Construct(
    function (const Left, Right: TProductCodeCount): Integer
      begin
        Result := CompareStr(Left.productCode, Right.productCode);
      end));

Для редактирования записей на месте вы можете использовать свойство TList<T>.List, которое дает вам прямой доступ к базовому массиву TList<T> и позволяет вам напрямую изменять записи в списке:

var
  productCodes: TList<TProductCodeCount>;
....
inc(productCodes.List[productCodeIndex].count);

Свойство TList<T>.List было введено в XE3, для более ранних версий Delphi следующее расширение класса позволяет изменять записи на месте:

  TXList<T> = class(TList<T>)
  protected type
    PT = ^T;
  function GetPItem(index: Integer): PT;
  public
    property PItems[index: Integer]: PT read GetPItem;
  end;

function TXList<T>.GetPItem(index: Integer): PT;
begin
  Result := @FItems[index];
end;

var
  productCodes: TXList<TProductCodeCount>;
....
inc(productCodes.PItems[productCodeIndex].count);
person Dalija Prasnikar    schedule 16.03.2015
comment
Вам лучше использовать Result := CompareStr( ... ); - person Sir Rufo; 16.03.2015
comment
Гораздо проще использовать TComparer<T>.Construct и не нести накладные расходы на создание нового класса. - person David Heffernan; 16.03.2015
comment
@David Я предпочитаю классы, потому что их легче повторно использовать и тестировать, а код более читабелен. - person Dalija Prasnikar; 16.03.2015
comment
Вы можете повторно использовать компаратор, возвращенный Construct, и он так же удобочитаем. - person David Heffernan; 16.03.2015
comment
@David, для полноты ответа, я также включил создание компаратора. - person Dalija Prasnikar; 16.03.2015
comment
Я согласен с Дэвидом. Для пользовательских сравнений гораздо проще сгенерировать новый IComparer‹x› с помощью Construct, чем создавать совершенно новый класс. Anonmeth для Construct может быть простой именованной функцией или методом, а также специальным фрагментом кода, причем первый также можно использовать повторно. - person Rudy Velthuis; 16.03.2015
comment
@Dalija Это можно сделать с помощью TList<T>.List. Вот почему это свойство существует. Опять намного чище, чем создание еще одного класса - person David Heffernan; 16.03.2015