Как я могу быстрее искать пары имя / значение в Delphi TStringList?

я реализовал перевод языка в приложении, поместив все строки во время выполнения в TStringList с:

procedure PopulateStringList;
begin  
  EnglishStringList.Append('CAN_T_FIND_FILE=It is not possible to find the file');
  EnglishStringList.Append('DUMMY=Just a dummy record');
  // total of 2000 record appended in the same way
  EnglishStringList.Sorted := True; // Updated comment: this is USELESS!
end;

затем я получаю перевод, используя:

function GetTranslation(ResStr:String):String;
var
  iIndex : Integer;
begin
  iIndex := -1;
  iIndex :=  EnglishStringList.IndexOfName(ResStr);
  if iIndex >= 0 then
  Result :=  EnglishStringList.ValueFromIndex[iIndex] else
  Result := ResStr + ' (Translation N/A)';
end;

В любом случае при таком подходе требуется около 30 микросекунд, чтобы найти запись, есть ли лучший способ достичь того же результата?

UPDATE: для дальнейшего использования я пишу здесь новую реализацию, которая использует TDictionary, как предложено (работает с Delphi 2009 и новее):

procedure PopulateStringList;
begin  
  EnglishDictionary := TDictionary<String, String>.Create;
  EnglishDictionary.Add('CAN_T_FIND_FILE','It is not possible to find the file');
  EnglishDictionary.Add('DUMMY','Just a dummy record');
  // total of 2000 record appended in the same way
end;


function GetTranslation(ResStr:String):String;
var
  ValueFound: Boolean;
begin
  ValueFound:=  EnglishDictionary.TryGetValue(ResStr, Result);
  if not ValueFound then Result := Result + '(Trans N/A)';
end;

новая функция GetTranslation работает в 1000 раз быстрее (на моих 2000 примерах записей), чем первая версия.

5 ответов


в Delphi 2009 или более поздней версии я бы использовал TDictionary в дженериках.Коллекции. Также обратите внимание, что есть бесплатные инструменты, такие какhttp://dxgettext.po.dk/ для перевода приложений.


THashedStringList должно быть лучше, я думаю.


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

подробнее об этом см.: THashedStringList слабость, что дает несколько альтернатив.

Если у вас есть большой список, который может быть обновлен, вы можете попробовать GpStringHash by Габр, это не должно пересчитывать всю таблицу при каждом изменении.


Я думаю, что вы неправильно используете EnglishStringList(TStringList). Это отсортированный список, вы добавляете элементы (строки), вы сортируете его, но при поиске вы делаете это частичной строкой (только имя, с IndexOfName).

Если вы используете IndexOfName в отсортированном списке,TStringList не могу использовать поиск Dicotomic. Он использует последовательный поиск.

(это реализация IndexOfName)

  for Result := 0 to GetCount - 1 do
  begin
    S := Get(Result);
    P := AnsiPos('=', S);
    if (P <> 0) and (CompareStrings(Copy(S, 1, P - 1), Name) = 0) then Exit;
  end;

Я думаю, что это причина низкой производительности.
Альтернативой является использование 2 TStringList:
* Первый (сортированный) содержит только "имя" и указатель на второй список, содержащий значение; вы можете реализовать этот указатель на второй список, используя "указатель" свойства объекта.
* Второй (не отсортированный) список содержит значения.

при поиске вы делаете это в первом списке; в этом случае вы можете использовать найти метод. когда вы найдете имя, указатель (реализованный с помощью свойства Object) даст вам позицию во втором списке со значением.

в этом случае метод Find в отсортированном списке более эффективен, чем HashList (который должен выполнить функцию, чтобы получить позицию значения).

С уважением.

Pd: извините меня за ошибки с английским языком.


вы также можете использовать помощник класса для перепрограммирования функции "IndexOfName":

TYPE
  TStringsHelper = CLASS HELPER FOR TStrings
                     FUNCTION IndexOfName(CONST Name : STRING) : INTEGER;
                   END;

FUNCTION TStringsHelper.IndexOfName(CONST Name : STRING) : INTEGER;
  VAR
    SL  : TStringList ABSOLUTE Self;
    S,T : STRING;
    I   : INTEGER;

  BEGIN
    IF (Self IS TStringList) AND SL.Sorted THEN BEGIN
      S:=Name+NameValueSeparator;
      IF SL.Find(S,I) THEN
        Result:=I
      ELSE IF (I<0) OR (I>=Count) THEN
        Result:=-1
      ELSE BEGIN
        T:=SL[I];
        IF CompareStrings(COPY(T,1,LENGTH(S)),S)=0 THEN Result:=I ELSE Result:=-1
      END;
      EXIT
    END;
    Result:=INHERITED IndexOfName(Name)
  END;

(или реализовать его в потоке TStrings класса, если вам не нравятся помощники класса или не имеют их в Вашей версии Delphi).

Это будет использовать двоичный поиск в отсортированном TStringList и последовательный поиск в других классах TStrings.