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


84

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

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

Хтось має функцію, яка, на їх думку, працює?

Дякую

Відповіді:


86

Ви можете використовувати властивість 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 повинен був укладати будь-які поля пробілами з лапками. Якщо введення містить подвійні лапки навколо будь-яких полів із пробілами, результат правильний.
Gerry Coll,

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

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

67

Немає необхідності в розробці 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) згадує, я віддав би перевагу рішенню Франка
Вольф

50

Можна використовувати StrUtils.SplitString.

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

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

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

SplitString розбиває рядок на різні частини, розділені зазначеними символами-роздільниками. S - рядок, який потрібно розділити. Розділювачі - це рядок, що містить символи, визначені як роздільники.

SplitString повертає масив рядків типу System.Types.TStringDynArray, що містить розділені частини вихідного рядка.


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

3
функція SplitString (const S, Розділювачі: рядок): TStringDynArray; визначено у StrUtils.pas
alex

Я не можу включити файл StrUtils.pas (навіть коли він присутній).
шукач правди

Це приклад поділу рядка на "масив".
bvj

найкраще, що це приймає роздільник рядків на відміну від роздільників символів в інших відповідях.
user30478

42

За допомогою функції SysUtils.TStringHelper.Split , представленої в Delphi XE3:

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

Це розділить рядок із заданим роздільником на масив рядків.


18

Я завжди використовую щось подібне до цього:

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.
Вовк

Користувачі C ++ Builder 6: відповідна функціяStrutils::AnsiReplaceText
Wolf

Напрочуд просто. Працюючи в Delphi 7 з допомогою : list.Text := AnsiReplaceStr(source, delimiter, #13#10);.
AlainD

У Delphi 6 можна використовувати SysUtils.StringReplace
pyfyc

14

Подібно до функції Explode (), запропонованої Mef, але з кількома відмінностями (одну з яких я вважаю виправленням помилки):

  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. Я використовую наступний тест для вибуху: вибухнути 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 розподіл, за яким потенційно може
відбутися

Також ви не пояснюєте призначення параметра Limit. Я інтуїтивно очікував, що він встановить максимальну кількість підрядків, які будуть повернуті, коли насправді це, схоже, обмежує виявлення підрядків першим "Обмеженням" # символів у вхідному рядку. Це здається безглуздим, оскільки якщо вам потрібно було це зробити, ви можете просто оперувати Explode () над копією () необхідного підрядка. Використання обмеження для встановлення максимальної кількості підрядків було б набагато кориснішим.
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

Бібліотека кодів джедаїв забезпечує розширений StringList із вбудованою функцією розділення, яка може як додавати, так і замінювати існуючий текст. Він також надає інтерфейс, що враховується за посиланнями. Отже, це можна використовувати навіть у старих версіях 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.
Джон Бо

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.