Более быстрый способ разделения текста в Delphi TStringList

У меня есть приложение, которому нужно выполнять тяжелые манипуляции с текстом в TStringList. В основном мне нужно разделить текст разделителем; например, если у меня есть одна строка с 1000 символов, и этот разделитель встречается в этой строке 3 раза, мне нужно разделить ее на 3 строки. Разделитель может содержать более одного символа, например, это может быть тег, например «[test]».

Я написал две функции для выполнения этой задачи с двумя разными подходами, но обе они медленны при больших объемах текста (обычно более 2 мегабайт).

Как я могу достичь этой цели быстрее?

Вот обе функции, обе получают 2 параметра: «lines», который является исходным списком tstringlist, и «q», который является разделителем.

function splitlines(lines : tstringlist; q: string) : integer;
var
  s, aux, ant : string;
  i,j : integer;
  flag : boolean;
  m2 : tstringlist;
begin
  try
    m2 := tstringlist.create;
    m2.BeginUpdate;
    result := 0;
    for i := 0 to lines.count-1 do
    begin
      s := lines[i];
      for j := 1 to length(s) do
      begin
        flag := lowercase(copy(s,j,length(q))) = lowercase(q);
        if flag then
        begin
          inc(result);
          m2.add(aux);
          aux := s[j];
        end
        else
          aux := aux + s[j];
      end;
      m2.add(aux);
      aux := '';
    end;
    m2.EndUpdate;
    lines.text := m2.text;
  finally
    m2.free;
  end;
end;


function splitLines2(lines : tstringlist; q: string) : integer;
var
  aux, p : string;
  i : integer;
  flag : boolean;
begin
  //maux1 and maux2 are already instanced in the parent class
  try
    maux2.text := lines.text;
    p := '';
    i := 0;
    flag := false;
    maux1.BeginUpdate;
    maux2.BeginUpdate;
    while (pos(lowercase(q),lowercase(maux2.text)) > 0) and (i < 5000) do
    begin
      flag := true;
      aux := p+copy(maux2.text,1,pos(lowercase(q),lowercase(maux2.text))-1);
      maux1.add(aux);
      maux2.text := copy(maux2.text,pos(lowercase(q),lowercase(maux2.text)),length(maux2.text));
      p := copy(maux2.text,1,1);
      maux2.text := copy(maux2.text,2,length(maux2.text));
      inc(i);
    end;
  finally
    result := i;
    maux1.EndUpdate;
    maux2.EndUpdate;
    if flag then
    begin
      maux1.add(p+maux2.text);
      lines.text := maux1.text;
    end;
  end;
end;

person delphirules    schedule 24.10.2013    source источник
comment
Проблема в том, что мой разделитель имеет более одного символа, например, это может быть целое слово.   -  person delphirules    schedule 24.10.2013
comment
Включите все рассматриваемые требования. Кстати, поставьте попытку после вызова конструктора.   -  person David Heffernan    schedule 24.10.2013
comment
Вы можете найти мой ответ на этот вопрос полезным: ="как разбить строку на многосимвольный разделитель"> stackoverflow.com/questions/15424293/   -  person Uwe Raabe    schedule 24.10.2013


Ответы (4)


Я не проверял скорость, но для академических целей есть простой способ разделить строки:

myStringList.Text :=
  StringReplace(myStringList.Text, myDelimiter, #13#10, [rfReplaceAll]);
// Use [rfReplaceAll, rfIgnoreCase] if you want to ignore case

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

person Marcus Adams    schedule 24.10.2013
comment
Человек, спасибо вам навсегда! Вы только что сделали мое приложение НАМНОГО ЛУЧШЕ! :D - person delphirules; 24.10.2013
comment
@Marcus Adams IIRC, StringReplace в Unicode Delphi (т. е. без поддержки FastCode) работает очень медленно, когда размер строки становится больше нескольких мегабайт. - person SOUser; 25.10.2013
comment
@XichenLi: Тогда хорошо, что теги для этого вопроса включают «delphi-2007» :-) - person Ken White; 25.10.2013
comment
@KenWhite Действительно. (PS: если разделителем является только один символ, пространство можно обменять на время, даже если используется Unicode Delphi. :D) - person SOUser; 25.10.2013
comment
Вы также можете использовать свойства Delimiter, StrictDelimiter и DelimitedText элемента TStringList. - person Jens Mühlenhoff; 25.10.2013
comment
Просто новое примечание к этому вопросу. Это решение работает нормально, но ищите модуль FastStrings, это намного быстрее. - person delphirules; 28.01.2015

Проблемы с вашим кодом (по крайней мере, второй подход)

  • Вы постоянно используете нижний регистр, который работает медленно, если вызывается так много раз
  • Если я правильно понял, вы копируете весь оставшийся текст обратно в первоисточник. Это обязательно будет очень медленным для больших строк (например, файлов)

У меня есть токенизатор в моей библиотеке. Это не самый быстрый или лучший способ, но он должен работать (вы можете получить его из Библиотека Cromis, просто используйте модули Cromis.StringUtils и Cromis.Unicode):

type
  TTokens = array of ustring;

  TTextTokenizer = class
  private
    FTokens: TTokens;
    FDelimiters: array of ustring;
  public
    constructor Create;
    procedure Tokenize(const Text: ustring);
    procedure AddDelimiters(const Delimiters: array of ustring);
    property Tokens: TTokens read FTokens;
  end;

{ TTextTokenizer }

procedure TTextTokenizer.AddDelimiters(const Delimiters: array of ustring);
var
  I: Integer;
begin
  if Length(Delimiters) > 0 then
  begin
    SetLength(FDelimiters, Length(Delimiters));

    for I := 0 to Length(Delimiters) - 1 do
      FDelimiters[I] := Delimiters[I];
  end;
end;

constructor TTextTokenizer.Create;
begin
  SetLength(FTokens, 0);
  SetLength(FDelimiters, 0);
end;

procedure TTextTokenizer.Tokenize(const Text: ustring);
var
  I, K: Integer;
  Counter: Integer;
  NewToken: ustring;
  Position: Integer;
  CurrToken: ustring;
begin
  SetLength(FTokens, 100);
  CurrToken := '';
  Counter := 0;

  for I := 1 to Length(Text) do
  begin
    CurrToken := CurrToken + Text[I];

    for K := 0 to Length(FDelimiters) - 1 do
    begin
      Position := Pos(FDelimiters[K], CurrToken);

      if Position > 0 then
      begin
        NewToken := Copy(CurrToken, 1, Position - 1);

        if NewToken <> '' then
        begin
          if Counter > Length(FTokens) then
            SetLength(FTokens, Length(FTokens) * 2);

          FTokens[Counter] := Trim(NewToken);
          Inc(Counter)
        end;

        CurrToken := '';
      end;
    end;
  end;

  if CurrToken <> '' then
  begin
    if Counter > Length(FTokens) then
      SetLength(FTokens, Length(FTokens) * 2);

    FTokens[Counter] := Trim(CurrToken);
    Inc(Counter)
  end;

  SetLength(FTokens, Counter);
end;
person Runner    schedule 24.10.2013

Как насчет использования StrTokens из библиотеки JCL?

процедура StrTokens (const S: строка; const List: TStrings);

Это открытый исходный код http://sourceforge.net/projects/jcl/.

person Sigurdur    schedule 24.10.2013

В качестве дополнительной опции вы можете использовать регулярные выражения. Последние версии Delphi (XE4 и XE5) имеют встроенную поддержку регулярных выражений; для более старых версий можно найти бесплатную загрузку библиотеки регулярных выражений (zip-файл) по адресу Регулярные-выражения.info.

Для встроенной поддержки регулярных выражений (использует общий TArray<string>):

var
  RegexObj: TRegEx;
  SplitArray: TArray<string>;
begin
  SplitArray := nil;
  try
    RegexObj := TRegEx.Create('\[test\]'); // Your sample expression. Replace with q
    SplitArray := RegexObj.Split(Lines, 0);
  except
    on E: ERegularExpressionError do begin
    // Syntax error in the regular expression
    end;
  end;
  // Use SplitArray
end;

Для использования TPerlRegEx в более ранних версиях Delphi:

var
  Regex: TPerlRegEx;
  m2: TStringList;
begin
  m2 := TStringList.Create;
  try
    Regex := TPerlRegEx.Create;
    try
      Regex.RegEx := '\[test\]';  //  Using your sample expression - replace with q
      Regex.Options := [];
      Regex.State := [preNotEmpty];
      Regex.Subject := Lines.Text;
      Regex.SplitCapture(m2, 0);
    finally
      Regex.Free;
    end;
    // Work with m2
  finally
    m2.Free;
  end;
end;

(Для тех, кто не знает, \ в примере выражения используется потому, что символы [] имеют смысл в регулярных выражениях и должны быть экранированы, чтобы использоваться в тексте регулярного выражения. Обычно они не требуются в тексте.)

person Ken White    schedule 25.10.2013