Разделить строку на массив строк на основе разделителя


85

Я пытаюсь найти функцию Delphi, которая разделит входную строку на массив строк на основе разделителя. Я нашел много в Google, но у всех, похоже, есть свои проблемы, и мне не удалось заставить их работать.

Мне просто нужно разбить строку вроде: "word:doc,txt,docx"на массив на основе ':'. Результат был бы ['word', 'doc,txt,docx'].

У кого-нибудь есть функция, которая, как они знают, работает?

Спасибо

Ответы:


87

вы можете использовать свойство 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.


22
К сожалению, во многих «старых» версиях Delphi есть ошибка (не знаю, в какой версии она была исправлена), которая приводит к тому, что пробел всегда используется в качестве разделителя. Так что обращайтесь с этим осторожно!
Лев,

16
Да уж. Вы захотите установить для StrictDelimiter значение true, и если свойство StrictDelimiter недоступно в вашей версии Delphi, не используйте эту технику! Но если да, то это очень полезно.
Мейсон Уиллер,

3
Это не было ошибкой, это было (раздражающим) дизайнерским решением еще в D1 или D2. CommaText должен был заключать любые поля пробелами в кавычки. Если при вводе любые поля с пробелами заключены в двойные кавычки, результат правильный.
Джерри Колл,

1
Одна из моих любимых мозолей - это когда люди без нужды помещают указатели типа в имена переменных / параметров. Паскаль строго типизирован - это избыточная типизация (из разнообразия упражнений с пальцами) и вводящая в заблуждение, когда индикатор типа неправильный, как в этом случае: ArrayOfStrings не является массивом (и как таковой даже не отвечает на поставленный вопрос) .
Deltics

6
Для всех, кто поддерживает этот ответ, обратите внимание, что он не дает массив, как указано в вопросе. Неполная спецификация требований - большая проблема в этой отрасли, игнорирование заявленных требований и предоставление того, чего не просили, - еще одна большая проблема. Одобрение любого из них просто способствует плохой практике. ;)
Deltics

69

Нет необходимости в разработке 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) я бы предпочел решение Фрэнка
Вольф

51

Вы можете использовать StrUtils.SplitString.

function SplitString(const S, Delimiters: string): TStringDynArray;

Его описание из документации :

Разбивает строку на разные части, разделенные указанными символами-разделителями.

SplitString разбивает строку на разные части, разделенные указанными символами-разделителями. S - строка, которую нужно разделить. Разделители - это строка, содержащая символы, определенные как разделители.

SplitString возвращает массив строк типа System.Types.TStringDynArray, который содержит разделенные части исходной строки.


3
Хммм, не в моей версии Delphi 2010 (есть подпрограмма SplitString в XMLDoc и в (Indy unit) IdStrings, но ни один из них не делает то, что хочет плакат, и процедура XMLDoc в любом случае не отображается через интерфейс модуля).
Deltics

3
функция SplitString (const S, Delimiters: string): TStringDynArray; определено в StrUtils.pas
alex

Я не могу включить файл StrUtils.pas (даже если он есть).
Правдоподобный 01

Это пример разделения строки на «массив».
bvj

Лучше всего то, что это принимает разделитель строк в отличие от разделителей символов в других ответах.
user30478 05

42

Использование функции SysUtils.TStringHelper.Split , представленной в Delphi XE3:

var
  MyString: String;
  Splitted: TArray<String>;
begin
  MyString := 'word:doc,txt,docx';
  Splitted := MyString.Split([':']);
end.

Это разделит строку с заданным разделителем на массив строк.


19

Я всегда использую что-то подобное:

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

В Delphi 6 можно использовать SysUtils.StringReplace
pyfyc

16

Подобна функции 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

Это конечный разделитель и условный «пустой последний элемент», который я считаю исправлением ошибки.

Я также включил изменение распределения памяти, которое я предложил, с уточнением (я ошибочно предположил, что входная строка может содержать не более 50% разделителей, но, возможно, она, конечно, может состоять из 100% строк разделителей, что дает массив пустых элементов!)


7

Explode - очень высокоскоростная функция, исходный алгоритм которой можно получить из компонента TStrings. Я использую следующий тест для взрыва: Explode 134217733 байта данных, я получаю 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

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

Строки в списке si будут содержать разделенные строки.


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

Вот реализация функции разнесения, которая доступна во многих других языках программирования как стандартная функция:

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. Я интуитивно ожидал, что он установит максимальное количество возвращаемых подстрок, хотя на самом деле он, похоже, ограничивает обнаружение подстрок первым «предельным» числом символов во входной строке. Это кажется бессмысленным, поскольку, если вам нужно это сделать, вы можете просто использовать Explode () над Copy () требуемой подстроки. Использование Limit для установки максимального количества подстрок было бы гораздо более полезным.
Deltics

@Deltics: Никто не утверждал, что это высокооптимизированная функция, и никто не просил об этом, поэтому я не понимаю вашу жалобу. Но, возможно, вы один из тех, кто оптимизирует все, независимо от того, нужно это или нет ...
Лев,

1
Я из тех парней, которые не пишут излишне неэффективный код, а потом не думают об оптимизации. Речь шла не о тщательном анализе кода и поиске небольшого потенциала оптимизации, это была просто очевидная и легко решаемая неэффективность: постепенный рост непрерывной памяти, которую вместо этого можно легко предварительно выделить и впоследствии усечь.
Deltics

Также @Mef: И это не была жалоба, это был комментарий, наблюдение. Но что более важно, ваш код также содержал то, что я считаю ошибкой (см. Мою альтернативу для объяснения).
Deltics

5

Я написал эту функцию, которая возвращает связанный список строк, разделенных определенным разделителем. Чистый бесплатный паскаль без модулей.

Program split_f;

type
    PTItem = ^TItem;
    TItem = record
        str : string;
        next : PTItem;
    end;

var
    s : string;
    strs : PTItem;

procedure split(str : string;delim : char;var list : PTItem);
var
    i : integer;
    buff : PTItem;
begin
    new(list);
    buff:= list;
    buff^.str:='';
    buff^.next:=nil;

    for i:=1 to length(str) do begin
        if (str[i] = delim) then begin
            new(buff^.next);
            buff:=buff^.next;
            buff^.str := '';
            buff^.next := nil;
        end
        else
        buff^.str:= buff^.str+str[i];
    end;
end;

procedure print(var list:PTItem);
var
    buff : PTItem;
begin
    buff := list;
    while buff<>nil do begin
        writeln(buff^.str);
        buff:= buff^.next;
    end;
end;

begin

    s := 'Hi;how;are;you?';

    split(s, ';', strs);
    print(strs);


end.

3

Библиотека Jedi Code Library предоставляет расширенный StringList со встроенной функцией Split, которая способна как добавлять, так и заменять существующий текст. Он также предоставляет интерфейс с подсчетом ссылок. Таким образом, это можно использовать даже со старыми версиями Delphi, в которых нет SplitStrings, и без осторожной и немного утомительной настройки стандартного TStringList для использования только указанных разделителей.

Например, данный текстовый файл с такими строками, как Dog 5 4 7один, можно проанализировать, используя:

var slF, slR: IJclStringList; ai: TList<integer>; s: string; i: integer;
    action: procedure(const Name: string; Const Data: array of integer);

slF := TJclStringList.Create; slF.LoadFromFile('some.txt');
slR := TJclStringList.Create;
for s in slF do begin
    slR.Split(s, ' ', true);
    ai := TList<Integer>.Create;
    try
       for i := 1 to slR.Count - 1 do
           ai.Add(StrToInt(slR[i]));
       action(slR[0], ai.ToArray);
    finally ai.Free; end;
end; 

http://wiki.delphi-jedi.org/wiki/JCL_Help:IJclStringList.Split@string@string@Boolean


3

Это решит вашу проблему

interface
   TArrayStr = Array Of string;

implementation

function SplitString(Text: String): TArrayStr;
var
   intIdx: Integer;
   intIdxOutput: Integer;
const
   Delimiter = ';';
begin
   intIdxOutput := 0;
   SetLength(Result, 1);
   Result[0] := ''; 

   for intIdx := 1 to Length(Text) do
   begin
      if Text[intIdx] = Delimiter then
      begin
         intIdxOutput := intIdxOutput + 1;
         SetLength(Result, Length(Result) + 1);
      end
      else
         Result[intIdxOutput] := Result[intIdxOutput] + Text[intIdx];
   end;
end;

Не могли бы вы объяснить, что делает код? Спасибо
Пако

он проходит через переданную строку в поисках разделителя const, если не найден, объединяется с текущей позицией в массиве, при нахождении переходит к следующей позиции в динамическом массиве
Деннис

1

Моя любимая функция для разделения:

procedure splitString(delim: char; s: string; ListOfStrings: TStrings);
var temp: string;
    i: integer;
begin
   ListOfStrings.Clear;
   for i:=1 to length(s) do
    begin
      if s[i] = delim then
        begin
          ListOfStrings.add(temp);
          temp := '';
        end
      else
        begin
          temp := temp + s[i];
          if i=length(s) then
             ListOfStrings.add(temp);
        end;
    end;
    ListOfStrings.add(temp);
end;

1
Последний элемент был пропущен в вашей функции
alijunior

1
Вам нужно добавить ListOfStrings.add(temp);после цикла, чтобы добавить последний элемент.
rnso

Спасибо за заметку, я редактировал код в блоке else.
John Boe

0

*

//Basic functionality of a TStringList solves this:


uses Classes  //TStringList 
    ,types    //TStringDynArray
    ,SysUtils //StringReplace()
    ;

....

 //--------------------------------------------------------------------------
 function _SplitString(const s:string; const delimiter:Char):TStringDynArray;
  var sl:TStringList;
      i:integer;
  begin
  sl:=TStringList.Create;

  //separete delimited items by sLineBreak;TStringlist will do the job:
  sl.Text:=StringReplace(s,delimiter,sLineBreak,[rfReplaceAll]);

  //return the splitted string as an array:
  setlength(Result,sl.count);
  for i:=0 to sl.Count-1
   do Result[i]:=sl[i];

  sl.Free;
  end;



//To split a FileName (last item will be the pure filename itselfs):

 function _SplitPath(const fn:TFileName):TStringDynArray;
  begin
  result:=_SplitString(fn,'\');
  end;

*


0

В базе ответа NGLG https://stackoverflow.com/a/8811242/6619626 вы можете использовать следующую функцию:

type
OurArrayStr=array of string;

function SplitString(DelimeterChars:char;Str:string):OurArrayStr;
var
seg: TStringList;
i:integer;
ret:OurArrayStr;
begin
    seg := TStringList.Create;
    ExtractStrings([DelimeterChars],[], PChar(Str), seg);
    for i:=0 to seg.Count-1 do
    begin
         SetLength(ret,length(ret)+1);
         ret[length(ret)-1]:=seg.Strings[i];
    end;
    SplitString:=ret;
    seg.Free;
end;

Работает во всех версиях Delphi.


0

Для delphi 2010 вам нужно создать свою собственную функцию разделения.

function Split(const Texto, Delimitador: string): TStringArray;
var
  i: integer;
  Len: integer;
  PosStart: integer;
  PosDel: integer;
  TempText:string;
begin
  i := 0;
  SetLength(Result, 1);
  Len := Length(Delimitador);
  PosStart := 1;
  PosDel := Pos(Delimitador, Texto);
  TempText:=  Texto;
  while PosDel > 0 do
    begin
      Result[i] := Copy(TempText, PosStart, PosDel - PosStart);
      PosStart := PosDel + Len;
      TempText:=Copy(TempText, PosStart, Length(TempText));
      PosDel := Pos(Delimitador, TempText);
      PosStart := 1;
      inc(i);
      SetLength(Result, i + 1);
    end;
  Result[i] := Copy(TempText, PosStart, Length(TempText));
end;

Вы можете называть его таковым

type
  TStringArray = array of string;
var Temp2:TStringArray;
Temp1="hello:world";
Temp2=Split(Temp1,':')

0
procedure SplitCSV(S:STRING;out SL:TStringList);
var c,commatext:string;
  a,b,up:integer;
begin
   c:=s.Replace(' ','<SPACE>');   //curate spaces

   //first ocurrence of "
   a:=pos('"',c);
   b:=pos('"',c,a+1);
   if (a>0) and (b>0) then
   begin
     commatext:=commatext+copy(c,0,a-1);
     commatext:=commatext+copy(c,a,b-a+1).Replace(',','<COMMA>');   //curate commas
     up:=b+1;
   end
   else
     commatext:=c;

   //while continue discovering "
   while (a>0) and (b>0) do
   begin
     a:=Pos('"',c,b+1);
     b:=pos('"',c,a+1);
     if (a>0) and (b>0) then
     begin
       commatext:=commatext+copy(c,up,a-up);
       commatext:=commatext+copy(c,a,b-a+1).Replace(',','<COMMA>'); //curate commas
       up:=b+1;
     end;
   end;
   //last piece of text end  
   if up<c.Length then
     commatext:=commatext+copy(c,up,c.Length-up+1);

   //split text using CommaText
   sl.CommaText:=commatext;

   sl.Text:=sl.Text.Replace('<COMMA>',',');   //curate commas
   sl.Text:=sl.Text.Replace('<SPACE>',' ');   //curate spaces
end;

Ответы, которые ясно и лаконично объясняют решение, гораздо полезнее, чем ответы, содержащие только код.
MartynA

0
interface

uses
  Classes;

type
  TStringArray = array of string;

  TUtilStr = class
    class function Split(const AValue: string; const ADelimiter: Char = ';'; const AQuoteChar: Char = '"'): TStringArray; static;
  end;


implementation

{ TUtilStr }

class function TUtilStr.Split(const AValue: string; const ADelimiter: Char; const AQuoteChar: Char): TStringArray;
var
  LSplited: TStringList;
  LText: string;
  LIndex: Integer;
begin
  LSplited := TStringList.Create;
  try
    LSplited.StrictDelimiter := True;
    LSplited.Delimiter := ADelimiter;
    LSplited.QuoteChar := AQuoteChar;
    LSplited.DelimitedText := AValue;

    SetLength(Result, LSplited.Count);
    for LIndex := 0 to LSplited.Count - 1 do
    begin
      Result[LIndex] := LSplited[LIndex];
    end;
  finally
    LSplited.Free;
  end;
end;

end.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.