2013-10-24 15 views
5

Tôi có một ứng dụng cần thao tác văn bản nặng trong TStringList. Về cơ bản tôi cần phải tách văn bản bằng dấu phân cách; Ví dụ, nếu tôi có một dòng singe với 1000 ký tự và dấu phân cách này xảy ra 3 lần trong dòng này, sau đó tôi cần phải chia nó trong 3 dòng. Dấu phân cách có thể chứa nhiều hơn một char, nó có thể là một thẻ như '[test]' chẳng hạn.Cách nhanh hơn để tách văn bản trong Delphi TStringList

Tôi đã viết hai chức năng để thực hiện tác vụ này với 2 cách tiếp cận khác nhau, nhưng cả hai đều chậm với số lượng lớn văn bản (nhiều hơn 2 triệu byte thường).

Làm cách nào để đạt được mục tiêu này một cách nhanh hơn?

Dưới đây là cả hai chức năng, cả hai nhận được 2 paramaters: 'lines' là tstringlist gốc và 'q' là dấu tách.

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; 
+0

Vấn đề là dấu phân cách của tôi có nhiều hơn một char, ví dụ: đó có thể là toàn bộ từ. – delphirules

+3

Bao gồm tất cả các yêu cầu được đề cập. Btw, đặt thử sau khi gọi hàm tạo. –

+2

Bạn có thể tìm thấy câu trả lời cho câu hỏi này có thể sử dụng được: http://stackoverflow.com/questions/15424293/how-to-split-string-by-a-multi-character-delimiter/15427587#15427587 –

Trả lời

15

tôi đã không kiểm tra tốc độ, nhưng đối với mục đích học tập, đây là một cách dễ dàng để chia chuỗi:

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

Khi bạn đặt Text tài sản của TStringList , nó phân tích trên các dòng mới và chia tách ở đó, do đó chuyển đổi thành một chuỗi, thay thế dấu phân tách bằng các dòng mới, sau đó gán nó trở lại với thuộc tính Text.

+0

Con người, cảm ơn mãi mãi ! Bạn chỉ cần thực hiện ứng dụng của tôi MUER TỐT HƠN! : D – delphirules

+2

Ý tưởng tuyệt vời! +1 –

+0

@Marcus Adams IIRC, StringReplace trong unicode Delphi (tức là, không phải FastCode-enabled) là cực kỳ chậm khi kích thước chuỗi trở nên lớn hơn nhiều mega byte. – SOUser

2

Những vấn đề với mã của bạn (ít nhất là tiếp cận thứ hai) là

  • Bạn thường xuyên sử dụng lowecase đó là chậm nếu gọi rất nhiều lần
  • Nếu tôi thấy một cách chính xác bạn đang sao chép văn bản còn lại toàn bộ trở lại với nguồn gốc. Điều này chắc chắn sẽ chậm hơn đối với các chuỗi lớn (ví dụ: tệp)

Tôi có mã thông báo trong thư viện của mình. Nó không phải là nhanh nhất hoặc tốt nhất nhưng nó nên làm (bạn có thể lấy nó từ Cromis Library, chỉ cần sử dụng các đơn vị Cromis.StringUtils và 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; 
0

Là một tùy chọn bổ sung, bạn có thể sử dụng biểu thức thông thường. Các phiên bản gần đây của Delphi (XE4 và XE5) có kèm theo hỗ trợ biểu thức chính quy; các phiên bản cũ hơn có thể tìm thấy số miễn phí regex library download (zip file) tại Regular-Expressions.info.

Đối với built-in hỗ trợ regex (sử dụng chung 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; 

Để sử dụng TPerlRegEx trong các phiên bản Delphi trước đó:

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; 

(Đối với những người không biết, các \ trong mẫu biểu thức được sử dụng là vì các ký tự [] có ý nghĩa trong cụm từ thông dụng và cần phải được thoát để được sử dụng trong văn bản biểu thức chính quy.)

Các vấn đề liên quan