Как я могу быстрее искать пары имя / значение в 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 слабость, что дает несколько альтернатив.
Если у вас есть большой список, который может быть обновлен, вы можете попробовать 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.