Suddividi una stringa in una matrice di stringhe in base a un delimitatore


85

Sto cercando di trovare una funzione Delphi che divida una stringa di input in un array di stringhe in base a un delimitatore. Ho trovato molto su Google, ma tutti sembrano avere i propri problemi e non sono riuscito a far funzionare nessuno di loro.

Ho solo bisogno di dividere una stringa come: "word:doc,txt,docx"in un array basato su ':'. Il risultato sarebbe ['word', 'doc,txt,docx'].

Qualcuno ha una funzione che sa funziona?

Grazie

Risposte:


87

è possibile utilizzare la proprietà TStrings.DelimitedText per dividere una stringa

controlla questo campione

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.

AGGIORNARE

Vedere questo collegamento per una spiegazione di StrictDelimiter.


22
Sfortunatamente c'è un bug in molte versioni "vecchie" di Delphi (non sono sicuro con quale versione sia stato corretto) che ha l'effetto che il carattere spazio è sempre usato come delimitatore. Quindi gestiscilo con cura !!
Leone

16
Si. Ti consigliamo di impostare StrictDelimiter su true e se la proprietà StrictDelimiter non è disponibile nella tua versione di Delphi, non utilizzare questa tecnica! Ma se lo è, allora è molto utile.
Mason Wheeler

3
Non era un bug, era una (fastidiosa) decisione di progettazione nel D1 o D2. CommaText avrebbe dovuto racchiudere tutti i campi con spazi tra virgolette. Se l'input ha virgolette doppie attorno a qualsiasi campo con spazi, il risultato è corretto.
Gerry Coll

1
Uno dei miei problemi preferiti è quando le persone inseriscono inutilmente indicatori di tipo nei nomi di variabili / parametri. Pascal è fortemente tipizzato - è una digitazione ridondante (della varietà di esercizi con le dita) e fuorviante in modo confuso quando l'indicatore di tipo è sbagliato, come in questo caso: ArrayOfStrings non è un array (e come tale non risponde nemmeno alla domanda come posta) .
Deltics

6
Per tutti coloro che votano questa risposta, tieni presente che non produce un array, come specificato nella domanda. La specifica incompleta dei requisiti è un grosso problema in questo settore, ignorare i requisiti dichiarati e fornire qualcosa non richiesto è un altro grosso problema. L'approvazione di entrambi incoraggia semplicemente le cattive pratiche. ;)
Deltics

69

Non è necessario progettare una Splitfunzione. Essa esiste già, vedi: Classes.ExtractStrings.

Usalo in un modo seguente:

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.

E per rispondere pienamente alla domanda; Listrappresenta l'array desiderato con gli elementi:

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

14
ExtractStrings è molto rigido: "I ritorni a capo, i caratteri di nuova riga e le virgolette (singole o doppie) sono sempre trattati come separatori."; e "Nota: ExtractStrings non aggiunge stringhe vuote all'elenco."
awmross

Il problema non è progettare una splitfunzione, ma la necessità di un TStringsoggetto. E a causa dell'inflessibilità (@awmross) menziona, preferirei la soluzione di Frank
Wolf

51

Puoi usare StrUtils.SplitString.

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

La sua descrizione dalla documentazione :

Divide una stringa in parti diverse delimitate dai caratteri delimitatori specificati.

SplitString divide una stringa in parti diverse delimitate dai caratteri delimitatori specificati. S è la stringa da dividere. Delimitatori è una stringa contenente i caratteri definiti come delimitatori.

SplitString restituisce una matrice di stringhe di tipo System.Types.TStringDynArray che contiene le parti divise della stringa originale.


3
Hmmm, non nella mia versione di Delphi 2010 (c'è una routine SplitString in XMLDoc e in (unità Indy) IdStrings, ma nessuna di queste fa quello che vuole il poster e la routine XMLDoc non è comunque esposta attraverso l'interfaccia dell'unità).
Deltics

3
funzione SplitString (const S, Delimiters: string): TStringDynArray; definito in StrUtils.pas
alex

Non sono in grado di includere il file StrUtils.pas (anche se presente).
truthseeker

Questo è un esempio di divisione di una stringa in un "array".
bvj

la cosa migliore è che questo accetta un delimitatore di stringa rispetto ai delimitatori di caratteri in altre risposte.
user30478

42

Utilizzando la funzione SysUtils.TStringHelper.Split , introdotta in Delphi XE3:

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

Questo dividerà una stringa con un dato delimitatore in un array di stringhe.


19

Uso sempre qualcosa di simile a questo:

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
Ottima soluzione per gli utenti delle versioni precedenti di Delphi.
Wolf

Utenti di C ++ Builder 6: la funzione corrispondente èStrutils::AnsiReplaceText
Wolf

Incredibilmente semplice. Lavorando in Delphi 7 con: list.Text := AnsiReplaceStr(source, delimiter, #13#10);.
AlainD

In Delphi 6 è possibile utilizzare SysUtils.StringReplace
pyfyc

16

Simile alla funzione Explode () offerta da Mef, ma con un paio di differenze (una delle quali considero una correzione di bug):

  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;

Differenze:

  1. Il parametro aMax limita il numero di stringhe da restituire
  2. Se la stringa di input è terminata da un separatore, si considera che esista una stringa finale "vuota" nominale

Esempi:

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

È il separatore finale e il nozionale "elemento finale vuoto" che considero la correzione del bug.

Ho anche incorporato la modifica dell'allocazione della memoria che ho suggerito, con raffinatezza (ho erroneamente suggerito che la stringa di input potrebbe contenere al massimo il 50% di separatori, ma plausibilmente potrebbe ovviamente consistere di stringhe di separazione al 100%, producendo un array di elementi vuoti!)


7

Explode è una funzione ad altissima velocità, l'alhoritm di origine ottenuto dal componente TStrings. Uso il prossimo test per esplodere: Esplodi 134217733 byte di dati, ottengo 19173962 elementi, tempo di lavoro: 2984 ms.

Implode è una funzione a velocità molto bassa, ma lo scrivo facilmente.

{ ****************************************************************************** }
{  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
Questo non compila: Stringsnon è un tipo.
NGLN

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

Lines in SI lista conterrà stringhe a spacco.


6

Puoi creare la tua funzione che restituisce TArray di stringa:

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

Ecco un'implementazione di una funzione di esplosione che è disponibile in molti altri linguaggi di programmazione come funzione standard:

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; 

Utilizzo del campione:

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

2
Ci sono alcune scelte strane e potenzialmente estremamente inefficienti in questo codice per gestire / anticipare la durata del risultato. Aumentando la matrice dei risultati in modo incrementale, aumentano le possibilità di ridistribuzione e frammentazione della memoria. Più efficiente sarebbe impostare una lunghezza iniziale grande quanto potrebbe essere, ad esempio supponendo che la stringa di input sia composta da stringhe di separazione al 50% = Lunghezza (S) div (2 * Lunghezza (Separatore). Quindi impostala sul numero effettivo di elementi al termine 1 allocazione seguita potenzialmente da un singolo troncamento
Deltics

Inoltre non spieghi lo scopo del parametro Limit. Mi aspettavo intuitivamente che impostasse un numero massimo di sottostringhe da restituire quando in realtà sembra limitare il rilevamento delle sottostringhe al primo "Limite" # di caratteri nella stringa di input. Questo sembra inutile poiché se fosse necessario farlo, si potrebbe semplicemente operare Explode () su un Copy () della sottostringa richiesta. L'uso di Limite per impostare un numero massimo di sottostringhe sarebbe molto più utile.
Deltics

@Deltics: Nessuno ha affermato che questa sia una funzione altamente ottimizzata e nessuno ne ha chiesto uno, quindi in qualche modo non capisco il tuo reclamo. Ma forse sei uno dei ragazzi che ottimizza tutto, a prescindere che sia necessario o meno ...
Leone

1
Sono il tipo di persona che non scrive codice inutilmente inefficiente e quindi si preoccupa di ottimizzare in seguito. Non si trattava di analizzare minuziosamente il codice e trovare qualche minuscolo potenziale di ottimizzazione, era semplicemente un'inefficienza ovvia e facilmente risolvibile: crescita incrementale della memoria contigua che può invece essere facilmente pre-allocata e successivamente troncata.
Deltics

Anche @Mef: E non era una lamentela, era un commento, un'osservazione. Ma ancora più importante, il tuo codice conteneva anche quello che considererei un bug (vedi la mia alternativa per una spiegazione).
Deltics

5

Ho scritto questa funzione che restituisce un elenco collegato di stringhe separate da un delimitatore specifico. Pascal libero puro senza moduli.

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 fornisce una StringList migliorata con funzione Split incorporata, che è in grado sia di aggiungere che di sostituire il testo esistente. Fornisce inoltre un'interfaccia conteggio dei riferimenti. Quindi questo può essere utilizzato anche con le versioni precedenti di Delphi che non hanno SplitString e senza personalizzazioni attente e un po 'noiose di TStringList stock per utilizzare solo i delimitatori specificati.

Ad esempio, un dato file di testo di righe come Dog 5 4 7uno può analizzarli usando:

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

Questo risolverà il tuo problema

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;

Puoi per favore dare qualche spiegazione su cosa fa il codice? Grazie
Paco

scorre attraverso la stringa passata alla ricerca del delimitatore const, quando non trovato, si concatena con la posizione corrente sull'array, quando trova, salta alla posizione successiva nell'array dinamico
Dennis

1

La mia funzione preferita per la divisione:

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
L'ultimo elemento è stato perso nella tua funzione
alijunior

1
È necessario aggiungere ListOfStrings.add(temp);dopo il ciclo per aggiungere l'ultimo elemento.
rnso

Grazie per la nota, ho modificato il codice in else block.
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

Alla base della risposta NGLG https://stackoverflow.com/a/8811242/6619626 puoi utilizzare la seguente funzione:

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;

Funziona in tutte le versioni di Delphi.


0

Per delphi 2010, è necessario creare la propria funzione di divisione.

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;

Puoi riferirti ad esso come tale

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;

Le risposte che spiegano la soluzione in modo chiaro e succinto sono molto più utili di quelle di solo codice.
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.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.