Faster way to split text in Delphi TStringList

delphirules picture delphirules · Oct 24, 2013 · Viewed 9.7k times · Source

I have an app that needs to do heavy text manipulation in a TStringList. Basically i need to split text by a delimiter ; for instance, if i have a singe line with 1000 chars and this delimiter occurs 3 times in this line, then i need to split it in 3 lines. The delimiter can contain more than one char, it can be a tag like '[test]' for example.

I've wrote two functions to do this task with 2 different approaches, but both are slow in big amounts of text (more then 2mbytes usually).

How can i achieve this goal in a faster way ?

Here are both functions, both receive 2 paramaters : 'lines' which is the original tstringlist and 'q' which is the delimiter.

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;

Answer

Marcus Adams picture Marcus Adams · Oct 24, 2013

I've not tested the speed, but for academic purposes, here's an easy way to split the strings:

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

When you set the Text property of TStringList, it parses on new lines and splits there, so converting to a string, replacing the delimiter with new lines, then assigning it back to the Text property works.