基于分隔符将一个字符串拆分为字符串数组

92

我正在寻找一个Delphi函数,用于根据分隔符将输入字符串拆分为字符串数组。我在网上搜索了很多,但它们都有自己的问题,我无法让它们正常工作。

我只需要按':'来拆分一个字符串,例如: "word:doc,txt,docx" 拆分后得到的结果是 ['word', 'doc,txt,docx']。我该如何做?

21个回答

102
你可以使用 TStrings.DelimitedText 属性来分割字符串。
检查这个示例。
program Project28;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils;

procedure Split(Delimiter: Char; Str: string; ListOfStrings: TStrings) ;
begin
   ListOfStrings.Clear;
   ListOfStrings.Delimiter       := Delimiter;
   ListOfStrings.StrictDelimiter := True; // Requires D2006 or newer.
   ListOfStrings.DelimitedText   := Str;
end;


var
   OutPutList: TStringList;
begin
   OutPutList := TStringList.Create;
   try
     Split(':', 'word:doc,txt,docx', OutPutList) ;
     Writeln(OutPutList.Text);
     Readln;
   finally
     OutPutList.Free;
   end;
end.

更新

参见此链接,了解StrictDelimiter的说明。


23
很不幸,在许多“旧版”Delphi版本中存在一个错误(不确定是哪个版本修复了此问题),其结果是空格字符始终被用作分隔符。因此请小心处理!! - Leo
17
是的,你需要将StrictDelimiter设置为true,如果你的Delphi版本中没有StrictDelimiter属性,则不要使用这种技术!但是如果有的话,这非常实用。 - Mason Wheeler
3
在D1或D2早期,这不是一个漏洞,而是一个(令人烦恼的)设计决策。CommaText应该用引号括起任何包含空格的字段。如果输入的带有双引号的字段周围有空格,则结果是正确的。 - Gerry Coll
1
我最讨厌的事情之一是人们在变量/参数名称中不必要地放置类型指示器。Pascal是强类型语言 - 这是多余的打字练习,当类型指示器错误时会令人困惑和误导,就像在这种情况下:ArrayOfStrings 不是一个数组(因此甚至无法回答所提出的问题)。 - Deltics
6
请注意,对于投票支持此答案的所有人,请注意,它不会产生一个数组,正如问题中所指定的那样。不完整的需求规范是该行业中的一个大问题,忽略已声明的要求并交付未被要求的内容是另一个大问题。批准任何一种情况都会鼓励不良实践。;) - Deltics
显示剩余3条评论

79

不需要自己编写 Split 函数,因为它已经存在,您可以查看:Classes.ExtractStrings

以下是使用方法:

program Project1;

{$APPTYPE CONSOLE}

uses
  Classes;

var
  List: TStrings;
begin
  List := TStringList.Create;
  try
    ExtractStrings([':'], [], PChar('word:doc,txt,docx'), List);
    WriteLn(List.Text);
    ReadLn;
  finally
    List.Free;
  end;
end.

为了完全回答这个问题;List代表着所需的带有元素的数组:

List[0] = 'word'
List[1] = 'doc,txt,docx'

14
ExtractStrings 非常不灵活:"回车、换行符和引号字符(单引号或双引号)始终被视为分隔符。";并且 "注意:ExtractStrings 不会将空字符串添加到列表中。" - awmross
问题不在于工程化“split”函数,而在于需要一个“TStrings”对象。正如@awmross所提到的那样,由于其缺乏灵活性,我更喜欢Frank的解决方案 - Wolf
同时,ExtractStrings无法在字符串上拆分 - 只能在字符(或某些字符集)上进行拆分。 - Ian Boyd

59
你可以使用StrUtils.SplitString
function SplitString(const S, Delimiters: string): TStringDynArray;

以下内容来自文档:

将字符串按指定的分隔符拆分为不同的部分。

SplitString函数可以将字符串按指定的分隔符字符拆分为不同的部分。参数S表示需要被拆分的字符串,参数Delimiters表示作为分隔符的字符集合。

SplitString将返回一个字符串数组类型的结果,类型为System.Types.TStringDynArray,其中包含原始字符串被拆分后的各个部分。


3
在我的 Delphi 2010 版本中似乎没有这个功能(XMLDoc 中有 SplitString 程序和(Indy 单元)IdStrings 中也有,但这两个程序都不能满足帖子作者的需求,而且 XMLDoc 程序也无法通过单元接口公开)。 - Deltics
3
在StrUtils.pas文件中定义的SplitString函数,其作用是将字符串S按照指定的分隔符Delimiters进行拆分,并返回一个包含拆分后子字符串的动态数组TStringDynArray。 - alex
我无法包含文件StrUtils.pas(即使存在)。 - truthseeker
这是将一个字符串拆分为“数组”的示例。 - bvj
最好的事情是,与其他答案中使用字符分隔符不同,它接受字符串分隔符。 - user30478
不知道它是何时添加的。肯定不是在Delphi 5中添加的。Deltics也表示它在2010年之前不存在。 - Ian Boyd

55
使用在Delphi XE3中引入的SysUtils.TStringHelper.Split函数:
var
  MyString: String;
  Splitted: TArray<String>;
begin
  MyString := 'word:doc,txt,docx';
  Splitted := MyString.Split([':']);
end.

这将使用给定的分隔符将字符串拆分为字符串数组。

22

我通常使用类似于这样的代码:

Uses
   StrUtils, Classes;

Var
  Str, Delimiter : String;
begin
  // Str is the input string, Delimiter is the delimiter
  With TStringList.Create Do
  try
    Text := ReplaceText(S,Delim,#13#10);

    // From here on and until "finally", your desired result strings are
    // in strings[0].. strings[Count-1)

  finally
    Free; //Clean everything up, and liberate your memory ;-)
  end;

end;

2
适用于旧版Delphi用户的绝佳解决方案。 - Wolf
C++ Builder 6用户:相应的函数是Strutils::AnsiReplaceText - Wolf
惊人的简单。在 Delphi 7 中使用:list.Text := AnsiReplaceStr(source, delimiter, #13#10); - AlainD
1
在 Delphi 6 中可以使用 SysUtils.StringReplace。 - pyfyc
当你的文本已经包含 CRLF 时会失败 - Ian Boyd
显示剩余2条评论

17

类似于 Mef 提供的 Explode() 函数,但有几个不同之处(其中一个我认为是错误修复):

  type
    TArrayOfString = array of String;


  function SplitString(const aSeparator, aString: String; aMax: Integer = 0): TArrayOfString;
  var
    i, strt, cnt: Integer;
    sepLen: Integer;

    procedure AddString(aEnd: Integer = -1);
    var
      endPos: Integer;
    begin
      if (aEnd = -1) then
        endPos := i
      else
        endPos := aEnd + 1;

      if (strt < endPos) then
        result[cnt] := Copy(aString, strt, endPos - strt)
      else
        result[cnt] := '';

      Inc(cnt);
    end;

  begin
    if (aString = '') or (aMax < 0) then
    begin
      SetLength(result, 0);
      EXIT;
    end;

    if (aSeparator = '') then
    begin
      SetLength(result, 1);
      result[0] := aString;
      EXIT;
    end;

    sepLen := Length(aSeparator);
    SetLength(result, (Length(aString) div sepLen) + 1);

    i     := 1;
    strt  := i;
    cnt   := 0;
    while (i <= (Length(aString)- sepLen + 1)) do
    begin
      if (aString[i] = aSeparator[1]) then
        if (Copy(aString, i, sepLen) = aSeparator) then
        begin
          AddString;

          if (cnt = aMax) then
          begin
            SetLength(result, cnt);
            EXIT;
          end;

          Inc(i, sepLen - 1);
          strt := i + 1;
        end;

      Inc(i);
    end;

    AddString(Length(aString));

    SetLength(result, cnt);
  end;

区别:

  1. aMax参数限制返回的字符串数量
  2. 如果输入字符串以分隔符结尾,则认为存在一个名义上的“空”最终字符串

例子:

SplitString(':', 'abc') returns      :    result[0]  = abc

SplitString(':', 'a:b:c:') returns   :    result[0]  = a
                                          result[1]  = b
                                          result[2]  = c
                                          result[3]  = <empty string>

SplitString(':', 'a:b:c:', 2) returns:    result[0]  = a
                                          result[1]  = b

我认为修复的bug是尾随分隔符和虚拟的“空最后元素”。

我还采纳了我建议的内存分配更改,进行了细化(我错误地建议输入字符串最多可能包含50%的分隔符,但它当然可能由100%的分隔符字符串组成,从而产生一个空元素数组!)


7
var  
    su  : string;        // What we want split
    si  : TStringList;   // Result of splitting
    Delimiter : string;
    ...
    Delimiter := ';';
    si.Text := ReplaceStr(su, Delimiter, #13#10);

si列表中的行将包含已分割的字符串。


7

Explode是一个非常高速的函数,它的源算法来自TStrings组件。我使用以下测试用例进行explode操作:对134217733字节的数据进行explode,我得到了19173962个元素,工作时间为2984毫秒。

Implode是一个非常慢的函数,但我很容易就能实现它。

{ ****************************************************************************** }
{  Explode/Implode (String <> String array)                                      }
{ ****************************************************************************** }
function Explode(S: String; Delimiter: Char): Strings; overload;
var I, C: Integer; P, P1: PChar;
begin
    SetLength(Result, 0);
    if Length(S) = 0 then Exit;
    P:=PChar(S+Delimiter); C:=0;
    while P^ <> #0 do begin
       P1:=P;
       while (P^ <> Delimiter) do P:=CharNext(P);
       Inc(C);
       while P^ in [#1..' '] do P:=CharNext(P);
       if P^ = Delimiter then begin
          repeat
           P:=CharNext(P);
          until not (P^ in [#1..' ']);
       end;
    end;
    SetLength(Result, C);
    P:=PChar(S+Delimiter); I:=-1;
    while P^ <> #0 do begin
       P1:=P;
       while (P^ <> Delimiter) do P:=CharNext(P);
       Inc(I); SetString(Result[I], P1, P-P1);
       while P^ in [#1..' '] do P:=CharNext(P);
       if P^ = Delimiter then begin
          repeat
           P:=CharNext(P);
          until not (P^ in [#1..' ']);
       end;
    end;
end;

function Explode(S: String; Delimiter: Char; Index: Integer): String; overload;
var I: Integer; P, P1: PChar;
begin
    if Length(S) = 0 then Exit;
    P:=PChar(S+Delimiter); I:=1;
    while P^ <> #0 do begin
       P1:=P;
       while (P^ <> Delimiter) do P:=CharNext(P);
        SetString(Result, P1, P-P1);
        if (I <> Index) then Inc(I) else begin
           SetString(Result, P1, P-P1); Exit;
        end;
       while P^ in [#1..' '] do P:=CharNext(P);
       if P^ = Delimiter then begin
          repeat
           P:=CharNext(P);
          until not (P^ in [#1..' ']);
       end;
    end;
end;

function Implode(S: Strings; Delimiter: Char): String;
var iCount: Integer;
begin
     Result:='';
     if (Length(S) = 0) then Exit;
     for iCount:=0 to Length(S)-1 do
     Result:=Result+S[iCount]+Delimiter;
     System.Delete(Result, Length(Result), 1);
end;

3
无法编译:Strings 不是一个类型。 - NGLN

6
您可以编写自己的函数,该函数返回字符串的TArray:
function mySplit(input: string): TArray<string>;
var
  delimiterSet: array [0 .. 0] of char; 
     // split works with char array, not a single char
begin
  delimiterSet[0] := '&'; // some character
  result := input.Split(delimiterSet);
end;

5

这里提供了一个实现“explode”函数的代码,该函数在许多其他编程语言中作为标准函数使用:

type 
  TStringDynArray = array of String;

function Explode(const Separator, S: string; Limit: Integer = 0): TStringDynArray; 
var 
  SepLen: Integer; 
  F, P: PChar; 
  ALen, Index: Integer; 
begin 
  SetLength(Result, 0); 
  if (S = '') or (Limit < 0) then Exit; 
  if Separator = '' then 
  begin 
    SetLength(Result, 1); 
    Result[0] := S; 
    Exit; 
  end; 
  SepLen := Length(Separator); 
  ALen := Limit; 
  SetLength(Result, ALen); 

  Index := 0; 
  P := PChar(S); 
  while P^ <> #0 do 
  begin 
    F := P; 
    P := AnsiStrPos(P, PChar(Separator)); 
    if (P = nil) or ((Limit > 0) and (Index = Limit - 1)) then P := StrEnd(F); 
    if Index >= ALen then 
    begin 
      Inc(ALen, 5); 
      SetLength(Result, ALen); 
    end; 
    SetString(Result[Index], F, P - F); 
    Inc(Index); 
    if P^ <> #0 then Inc(P, SepLen); 
  end; 
  if Index < ALen then SetLength(Result, Index); 
end; 

示例用法:

var
  res: TStringDynArray;
begin
  res := Explode(':', yourString);

2
这段代码在管理/预测结果长度方面做出了一些奇怪且可能极其低效的选择。通过逐步增加结果数组的大小,增加了内存重新分配和碎片化的可能性。更有效的方法是将初始长度设置为可能的最大值,即假设输入字符串由50%的分隔符字符串组成= Length(S)div(2 * Length(Separator)。然后在完成时将其设置为实际项目数。1次分配,随后可能是单个截断。 - Deltics
你也没有解释Limit参数的目的。我的直觉认为它应该设置返回的子字符串的最大数量,但实际上它会限制在输入字符串中的前“Limit”个字符中检测子字符串。这似乎是毫无意义的,因为如果你需要这样做,你可以简单地对所需的子字符串进行Copy()操作并使用Explode()函数来完成。将Limit用于设置子字符串的最大数量将会更有用。 - Deltics
@Deltics:没有人声称这是一个高度优化的函数,也没有人要求如此,所以我有点不理解你的抱怨。但也许你是那些无论是否必要都会优化一切的人之一... - Leo
1
我是那种不会写毫无必要的低效代码,然后再担心优化的人。这并不是仔细分析代码并找到微小的优化潜力的情况,而只是一个明显且容易解决的低效率问题:连续内存的增量增长可以很容易地预先分配,然后进行截断。 - Deltics
还有@Mef:这不是抱怨,而是评论和观察。但更重要的是,你的代码也包含了我认为是一个错误(请参考我的替代方案进行解释)。 - Deltics

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接