Divide una cadena en una matriz de cadenas según un delimitador

84

Estoy tratando de encontrar una función Delphi que divida una cadena de entrada en una matriz de cadenas basada en un delimitador. He encontrado mucho en Google, pero todos parecen tener sus propios problemas y no he podido hacer que ninguno funcione.

Solo necesito dividir una cadena como: "word:doc,txt,docx"en una matriz basada en ':'. El resultado sería ['word', 'doc,txt,docx'].

¿Alguien tiene una función que sabe que funciona?

Gracias

Ryan
fuente

Respuestas:

86

puede usar la propiedad TStrings.DelimitedText para dividir una cadena

mira esta muestra

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.

ACTUALIZAR

Consulte este enlace para obtener una explicación de StrictDelimiter.

RRUZ
fuente
22
Desafortunadamente, hay un error en muchas versiones "antiguas" de Delphi (no estoy seguro con qué versión se corrigió esto) que tiene el efecto de que el carácter de espacio siempre se usa como delimitador. ¡¡Así que maneje esto con cuidado !!
Leo
16
Si. Querrá establecer StrictDelimiter en verdadero, y si la propiedad StrictDelimiter no está disponible en su versión de Delphi, ¡no use esta técnica! Pero si lo es, entonces esto es muy útil.
Mason Wheeler
3
No fue un error, fue una decisión de diseño (molesta) allá por D1 o D2. Se suponía que CommaText encerraba los campos con espacios entre comillas. Si la entrada tiene comillas dobles alrededor de cualquier campo con espacios, el resultado es correcto.
Gerry Coll
1
Una de mis cosas que me molestan es cuando la gente coloca innecesariamente indicadores de tipo en nombres de variables / parámetros. Pascal está fuertemente tipado: es tipeo redundante (de la variedad de ejercicios con los dedos) y confusamente engañoso cuando el indicador de tipo es incorrecto, como en este caso: ArrayOfStrings no es una matriz (y como tal ni siquiera responde la pregunta tal como se plantea) .
Deltics
6
Para todos los que votaron a favor de esta respuesta, tenga en cuenta que no produce una matriz, como se especifica en la pregunta. La especificación de requisitos incompleta es un gran problema en esta industria, ignorar los requisitos establecidos y entregar algo no solicitado es otro gran problema. Aprobar cualquiera de ellos simplemente fomenta las malas prácticas. ;)
Deltics
67

No es necesario diseñar una Splitfunción. Ya existe, véase: Classes.ExtractStrings.

Úselo de la siguiente manera:

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.

Y para responder la pregunta completamente; Listrepresenta la matriz deseada con los elementos:

List[0] = 'word'
List[1] = 'doc,txt,docx'
NGLN
fuente
14
ExtractStrings es muy inflexible: "Los retornos de carro, los caracteres de nueva línea y los caracteres de comillas (simples o dobles) siempre se tratan como separadores"; y "Nota: ExtractStrings no agrega cadenas vacías a la lista".
awmross
El problema no es diseñar una splitfunción, sino la necesidad de un TStringsobjeto. Y debido a la inflexibilidad que menciona (@awmross), preferiría la solución de Frank
Wolf
50

Puede utilizar StrUtils.SplitString.

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

Su descripción de la documentación :

Divide una cadena en diferentes partes delimitadas por los caracteres delimitadores especificados.

SplitString divide una cadena en diferentes partes delimitadas por los caracteres delimitadores especificados. S es la cuerda que se dividirá. Delimitadores es una cadena que contiene los caracteres definidos como delimitadores.

SplitString devuelve una matriz de cadenas de tipo System.Types.TStringDynArray que contiene las partes divididas de la cadena original.

alex
fuente
3
Hmmm, no en mi versión de Delphi 2010 (hay una rutina SplitString en XMLDoc y en (unidad Indy) IdStrings, pero ninguno de estos hace lo que quiere el cartel y la rutina XMLDoc no se expone a través de la interfaz de la unidad de todos modos).
Deltics
3
función SplitString (const S, Delimitadores: cadena): TStringDynArray; definido en StrUtils.pas
alex
No puedo incluir el archivo StrUtils.pas (incluso cuando está presente).
buscador de la verdad
Este ES un ejemplo de cómo dividir una cadena en una "matriz".
bvj
Lo mejor es que esto acepta un delimitador de cadena en lugar de los delimitadores de caracteres en otras respuestas.
user30478
42

Usando la función SysUtils.TStringHelper.Split , introducida en Delphi XE3:

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

Esto dividirá una cadena con un delimitador dado en una matriz de cadenas.

LU RD
fuente
18

Siempre uso algo similar a esto:

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;
Franco
fuente
2
Gran solución para usuarios de versiones anteriores de Delphi.
Wolf
Usuarios de C ++ Builder 6: la función correspondiente esStrutils::AnsiReplaceText
Wolf
Increíblemente simple. Trabajando en Delphi 7 con: list.Text := AnsiReplaceStr(source, delimiter, #13#10);.
AlainD
En Delphi 6 puede usar SysUtils.StringReplace
pyfyc
14

Similar a la función Explode () ofrecida por Mef, pero con un par de diferencias (una de las cuales considero una corrección de errores):

  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;

Diferencias:

  1. El parámetro aMax limita el número de cadenas que se devolverán
  2. Si la cadena de entrada termina con un separador, se considera que existe una cadena final "vacía" nominal

Ejemplos:

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

Es el separador final y el "elemento final vacío" teórico lo que considero la corrección del error.

También incorporé el cambio de asignación de memoria que sugerí, con refinamiento (sugerí erróneamente que la cadena de entrada podría contener como máximo un 50% de separadores, pero posiblemente podría consistir, por supuesto, en cadenas de separación al 100%, ¡produciendo una matriz de elementos vacíos!)

Deltics
fuente
7

Explode es una función de muy alta velocidad, la fuente se obtiene del componente TStrings. Utilizo la siguiente prueba para explotar: explotar 134217733 bytes de datos, obtengo 19173962 elementos, tiempo de trabajo: 2984 ms.

Implode es una función de muy baja velocidad, pero la escribo fácilmente.

{ ****************************************************************************** }
{  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;
Delphi 7
fuente
3
Esto no se compila: Stringsno es 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);

Las líneas de la lista si contendrán cadenas divididas.

Ihor Konovalenko
fuente
6

Puede crear su propia función que devuelve TArray de cadena:

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;
bob_saginowski
fuente
5

Aquí hay una implementación de una función de explosión que está disponible en muchos otros lenguajes de programación como función estándar:

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; 

Uso de muestra:

var
  res: TStringDynArray;
begin
  res := Explode(':', yourString);
León
fuente
2
Hay algunas opciones extrañas y potencialmente enormemente ineficientes en este código para administrar / anticipar la duración del resultado. Al hacer crecer la matriz de resultados de forma incremental, aumentan las posibilidades de reasignaciones de memoria y fragmentación. Más eficiente sería establecer una longitud inicial tan grande como sea posible, es decir, asumir que la cadena de entrada consta de un 50% de cadenas de separación = Longitud (S) div (2 * Longitud (Separador). Luego, establézcalo en el número real de elementos cuando se hace. 1 asignación seguida potencialmente por un solo truncamiento.
Deltics
Además, no explica el propósito del parámetro Limit. Intuitivamente esperaba que estableciera un número máximo de subcadenas para ser devueltas cuando, de hecho, parece restringir la detección de subcadenas al primer "Límite" # de caracteres en la cadena de entrada. Esto parece inútil ya que si necesitara hacerlo, simplemente podría operar Explode () sobre una Copia () de la subcadena requerida. Usar Limit para establecer un número máximo de subcadenas sería mucho más útil.
Deltics
@Deltics: Nadie afirmó que esta es una función altamente optimizada, y nadie pidió una, así que de alguna manera no entiendo su queja. Pero quizás eres de los chicos que optimizan todo, sin importar si es necesario o no ...
Leo
1
Soy el tipo de persona que no escribe código innecesariamente ineficiente y luego se preocupa por optimizarlo más tarde. Este no fue un caso de analizar el código minuciosamente y encontrar un minúsculo potencial de optimización, fue simplemente una ineficiencia obvia y fácil de abordar: el crecimiento incremental de la memoria contigua que, en cambio, se puede preasignar fácilmente y posteriormente truncar.
Deltics
También @Mef: Y no fue una queja, fue un comentario, una observación. Pero lo que es más importante, su código también contenía lo que yo consideraría un error (consulte mi alternativa para obtener una explicación).
Deltics
5

Escribí esta función que devuelve una lista vinculada de cadenas separadas por un delimitador específico. Pascal libre puro sin módulos.

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.
Aleš Oskar Kocur
fuente
3

Jedi Code Library proporciona una StringList mejorada con la función Split incorporada, que es capaz de agregar y reemplazar el texto existente. También proporciona una interfaz contada por referencias. Por lo tanto, esto se puede usar incluso con versiones anteriores de Delphi que no tienen SplitStrings y sin personalizaciones cuidadosas y un poco tediosas de TStringList estándar para usar solo delimitadores especificados.

Por ejemplo, un archivo de texto de líneas dado como Dog 5 4 7uno puede analizarlas 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

Arioch 'El
fuente
3

Esto resolverá tu 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;
Dennis
fuente
¿Puede dar alguna explicación sobre lo que hace el código? Gracias
Paco
corre a través de la cadena pasada buscando el delimitador const, cuando no se encuentra, concatena con la posición actual en la matriz, cuando se encuentra, salta a la siguiente posición en la matriz dinámica
Dennis
1

Mi función favorita para dividir:

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;
John Boe
fuente
1
El último elemento se perdió en su función
alijunior
1
Debe agregar ListOfStrings.add(temp);después del bucle para agregar el último elemento.
rnso
Gracias por la nota, edité el código en el bloque 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;

*

David Ulbrich
fuente
0

La base de NGLG responde https://stackoverflow.com/a/8811242/6619626 puede usar la siguiente función:

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;

Funciona en todas las versiones de Delphi.

Reza Mousavi
fuente
0

Para delphi 2010, debe crear su propia función de división.

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;

Puedes referirte a él como tal

type
  TStringArray = array of string;
var Temp2:TStringArray;
Temp1="hello:world";
Temp2=Split(Temp1,':')
usuario3609960
fuente
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;
Neri Bocchi
fuente
Las respuestas que explican la solución de forma clara y sucinta son mucho más útiles que las de solo código.
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.
marvinbraga
fuente