delphi lazarus freepascal

delphi - ¿Existe una forma simplista de extraer números de una cadena siguiendo ciertas reglas?



lazarus freepascal (5)

Aquí hay una solución usando regex. Lo implementé en Delphi (probado en 10.1, pero también debería funcionar con XE8), estoy seguro de que puedes adoptarlo para Lázaro, pero no estoy seguro de qué bibliotecas de expresiones regulares funcionan allí. El patrón de expresiones regulares usa la alternancia para hacer coincidir números como enteros o flotantes siguiendo sus reglas:

Entero:

(/b/d+(?![./d]))

  • comenzó con un límite de palabra (por lo tanto, no hay letras, números ni guiones bajos antes, si los guiones bajos son un problema que podría usar (?<![[:alnum:]]) )
  • luego une uno o más dígitos
  • que no están seguidos de dígitos ni puntos

Flotador:

(/b/d+(?:/./d+)?)

  • comenzó con un límite de palabra (por lo tanto, no hay letras, números ni guiones bajos antes, si los guiones bajos son un problema que podría usar (?<![[:alnum:]]) )
  • luego une uno o más dígitos
  • Opcionalmente, haga coincidir el punto seguido de otros dígitos.

Una aplicación de consola simple parece

program Test; {$APPTYPE CONSOLE} uses System.SysUtils, RegularExpressions; procedure ParseString(const Input: string); var Match: TMatch; begin WriteLn(''---start---''); Match := TRegex.Match(Input, ''(/b/d+(?![./d]))|(/b/d+(?:/./d+)?)''); while Match.Success do begin if Match.Groups[1].Value <> '''' then writeln(Match.Groups[1].Value + ''(Integer)'') else writeln(Match.Groups[2].Value + ''(Float)''); Match := Match.NextMatch; end; WriteLn(''---end---''); end; begin ParseString(''There are test values: P7 45.826.53.91.7, .5, 66.. 4 and 5.40.3.''); ParseString(''Anoth3r Te5.t string .4 abc 8.1Q 123.45.67.8.9''); ReadLn; end.

Necesito extraer números de una cadena y colocarlos en una lista; existen algunas reglas para esto, como identificar si el número extraído es un número entero o flotante.

La tarea parece bastante simple pero me encuentro cada vez más confundido a medida que pasa el tiempo y realmente podría hacerlo con cierta orientación.

Tome la siguiente cadena de prueba como ejemplo:

There are test values: P7 45.826.53.91.7, .5, 66.. 4 and 5.40.3.

Las reglas a seguir al analizar la cadena son las siguientes:

  • los números no pueden ser precedidos por una letra.

  • Si encuentra un número y no está seguido por un punto decimal, entonces el número es como un entero.

  • Si encuentra un número y le sigue un punto decimal, entonces el número es un flotante, por ejemplo, 5.

  • ~ Si más números siguen el punto decimal, entonces el número sigue siendo un flotante, por ejemplo, 5.40

  • ~ Otro punto decimal encontrado debería dividir el número, por ejemplo, 5.40.3 se convierte en (5.40 Flotante) y (3 Flotante)

  • En el caso de una letra, por ejemplo, después de un punto decimal, por ejemplo, 3.H continuación, agregue 3. como Flotante a la lista (incluso si técnicamente no es válido)

Ejemplo 1

Para hacer esto un poco más claro, tomar la cadena de prueba citada sobre la salida deseada debe ser como sigue:

En la imagen de arriba, el color azul claro ilustra los números de flotación, el rojo pálido ilustra los enteros únicos (pero también tenga en cuenta cómo los flotadores unidos se dividen en flotadores separados).

  • 45.826 (flotador)
  • 53.91 (flotador)
  • 7 (entero)
  • 5 (entero)
  • 66. (Flotador)
  • 4 (entero)
  • 5.40 (Flotador)
  • 3. (Flotador)

Tenga en cuenta que hay espacios deliberados entre 66. y 3. arriba debido a la forma en que se formatearon los números.

Ejemplo 2:

Anoth3r Te5.t string .4 abc 8.1Q 123.45.67.8.9

  • 4 (entero)
  • 8.1 (Flotador)
  • 123.45 (flotador)
  • 67.8 (Flotador)
  • 9 (entero)

Para dar una mejor idea, creé un nuevo proyecto durante la prueba que se parece a esto:

Ahora en la tarea real. Pensé que tal vez podría leer cada carácter de la cadena e identificar cuáles son los números válidos según las reglas anteriores, y luego agruparlos en una lista.

Para mi capacidad, esto era lo mejor que podía manejar:

El código es el siguiente:

unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls; type TForm1 = class(TForm) btnParseString: TButton; edtTestString: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; lstDesiredOutput: TListBox; lstActualOutput: TListBox; procedure btnParseStringClick(Sender: TObject); private FDone: Boolean; FIdx: Integer; procedure ParseString(const Str: string; var OutValue, OutKind: string); public { public declarations } end; var Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.ParseString(const Str: string; var OutValue, OutKind: string); var CH1, CH2: Char; begin Inc(FIdx); CH1 := Str[FIdx]; case CH1 of ''0''..''9'': // Found a number begin CH2 := Str[FIdx - 1]; if not (CH2 in [''A''..''Z'']) then begin OutKind := ''Integer''; // Try to determine float... //while (CH1 in [''0''..''9'', ''.'']) do //begin // case Str[FIdx] of // ''.'': // begin // CH2 := Str[FIdx + 1]; // if not (CH2 in [''0''..''9'']) then // begin // OutKind := ''Float''; // //Inc(FIdx); // end; // end; // end; //end; end; OutValue := Str[FIdx]; end; end; FDone := FIdx = Length(Str); end; procedure TForm1.btnParseStringClick(Sender: TObject); var S, SKind: string; begin lstActualOutput.Items.Clear; FDone := False; FIdx := 0; repeat ParseString(edtTestString.Text, S, SKind); if (S <> '''') and (SKind <> '''') then begin lstActualOutput.Items.Add(S + '' ('' + SKind + '')''); end; until FDone = True; end; end.

Claramente, no da el resultado deseado (el código fallido ha sido comentado) y mi enfoque probablemente sea incorrecto, pero creo que solo necesito hacer algunos cambios aquí y allá para una solución funcional.

En este punto, me he sentido bastante confundido y bastante perdido, a pesar de pensar que la respuesta es bastante cercana, la tarea se está volviendo cada vez más exasperante y realmente agradecería alguna ayuda.

EDITAR 1

Aquí me acerqué un poco más porque ya no hay números duplicados, pero el resultado sigue siendo claramente erróneo.

unit Unit1; {$mode objfpc}{$H+} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls; type TForm1 = class(TForm) btnParseString: TButton; edtTestString: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; lstDesiredOutput: TListBox; lstActualOutput: TListBox; procedure btnParseStringClick(Sender: TObject); private FDone: Boolean; FIdx: Integer; procedure ParseString(const Str: string; var OutValue, OutKind: string); public { public declarations } end; var Form1: TForm1; implementation {$R *.lfm} { TForm1 } // Prepare to pull hair out! procedure TForm1.ParseString(const Str: string; var OutValue, OutKind: string); var CH1, CH2: Char; begin Inc(FIdx); CH1 := Str[FIdx]; case CH1 of ''0''..''9'': // Found the start of a new number begin CH1 := Str[FIdx]; // make sure previous character is not a letter CH2 := Str[FIdx - 1]; if not (CH2 in [''A''..''Z'']) then begin OutKind := ''Integer''; // Try to determine float... //while (CH1 in [''0''..''9'', ''.'']) do //begin // OutKind := ''Float''; // case Str[FIdx] of // ''.'': // begin // CH2 := Str[FIdx + 1]; // if not (CH2 in [''0''..''9'']) then // begin // OutKind := ''Float''; // Break; // end; // end; // end; // Inc(FIdx); // CH1 := Str[FIdx]; //end; end; OutValue := Str[FIdx]; end; end; OutValue := Str[FIdx]; FDone := Str[FIdx] = #0; end; procedure TForm1.btnParseStringClick(Sender: TObject); var S, SKind: string; begin lstActualOutput.Items.Clear; FDone := False; FIdx := 0; repeat ParseString(edtTestString.Text, S, SKind); if (S <> '''') and (SKind <> '''') then begin lstActualOutput.Items.Add(S + '' ('' + SKind + '')''); end; until FDone = True; end; end.

Mi pregunta es ¿cómo puedo extraer números de una cadena, agregarlos a una lista y determinar si el número es entero o flotante?

El cuadro de lista verde pálido de la izquierda (salida deseada) muestra cuáles deberían ser los resultados, el cuadro de lista azul pálido de la derecha (salida real) muestra lo que realmente obtuvimos.

Por favor asesóreme, gracias.

Tenga en cuenta que volví a agregar la etiqueta Delphi, ya que uso XE7, así que no la elimine, aunque este problema en particular está en Lazarus, mi solución debería funcionar tanto para XE7 como para Lazarus.


Hay tantos errores básicos en su código que decidí corregir su tarea, por así decirlo. Esta no es una buena forma de hacerlo, pero al menos se eliminan los errores básicos. ¡Ten cuidado de leer los comentarios!

procedure TForm1.ParseString(const Str: string; var OutValue, OutKind: string); //var // CH1, CH2: Char; <<<<<<<<<<<<<<<< Don''t need these begin (************************************************* * * * This only corrects the ''silly'' errors. It is * * NOT being passed off as GOOD code! * * * *************************************************) Inc(FIdx); // CH1 := Str[FIdx]; <<<<<<<<<<<<<<<<<< Not needed but OK to use. I removed them because they seemed to cause confusion... OutKind := ''None''; OutValue := ''''; try case Str[FIdx] of ''0''..''9'': // Found the start of a new number begin // CH1 := Str[FIdx]; <<<<<<<<<<<<<<<<<<<< Not needed // make sure previous character is not a letter // >>>>>>>>>>> make sure we are not at beginning of file if FIdx > 1 then begin //CH2 := Str[FIdx - 1]; if (Str[FIdx - 1] in [''A''..''Z'', ''a''..''z'']) then // <<<<< don''t forget lower case! begin exit; // <<<<<<<<<<<<<< end; end; // else we have a digit and it is not preceeded by a number, so must be at least integer OutKind := ''Integer''; // <<<<<<<<<<<<<<<<<<<<< WHAT WE HAVE SO FAR >>>>>>>>>>>>>> OutValue := Str[FIdx]; // <<<<<<<<<<<<< Carry on... inc( FIdx ); // Try to determine float... while (Fidx <= Length( Str )) and (Str[ FIdx ] in [''0''..''9'', ''.'']) do // <<<<< not not CH1! begin OutValue := Outvalue + Str[FIdx]; //<<<<<<<<<<<<<<<<<<<<<< Note you were storing just 1 char. EVER! //>>>>>>>>>>>>>>>>>>>>>>>>> OutKind := ''Float''; ***** NO! ***** case Str[FIdx] of ''.'': begin OutKind := ''Float''; // now just copy any remaining integers - that is all rules ask for inc( FIdx ); while (Fidx <= Length( Str )) and (Str[ FIdx ] in [''0''..''9'']) do // <<<<< note ''.'' excluded here! begin OutValue := Outvalue + Str[FIdx]; inc( FIdx ); end; exit; end; // >>>>>>>>>>>>>>>>>>> all the rest in unnecessary //CH2 := Str[FIdx + 1]; // if not (CH2 in [''0''..''9'']) then // begin // OutKind := ''Float''; // Break; // end; // end; // end; // Inc(FIdx); // CH1 := Str[FIdx]; //end; end; inc( fIdx ); end; end; end; // OutValue := Str[FIdx]; <<<<<<<<<<<<<<<<<<<<< NO! Only ever gives 1 char! // FDone := Str[FIdx] = #0; <<<<<<<<<<<<<<<<<<< NO! #0 does NOT terminate Delphi strings finally // <<<<<<<<<<<<<<< Try.. finally clause added to make sure FDone is always evaluated. // <<<<<<<<<< Note there are better ways! if FIdx > Length( Str ) then begin FDone := TRUE; end; end; end;


La respuesta es bastante cercana, pero hay varios errores básicos. Para darle algunas sugerencias (sin escribir su código): dentro del ciclo while, DEBE SIEMPRE un incremento (el incremento no debería estar donde está, de lo contrario se obtiene un ciclo infinito) y DEBE verificar que no haya llegado al final de la cadena (de lo contrario obtendrías una excepción) y, finalmente, tu bucle while no debería depender de CH1, porque eso nunca cambia (lo que de nuevo da como resultado un bucle infinito). Pero mi mejor consejo aquí es rastrear a través de su código con el depurador, para eso está. Entonces tus errores se volverían obvios.


Sus reglas son bastante complejas, por lo que puede intentar construir una máquina de estados finitos (FSM, DFA - autómata finito determinista ).

Cada char provoca la transición entre estados.

Por ejemplo, cuando se encuentra en el estado "entero iniciado" y se encuentra con el espacio, se obtiene un valor entero y el FSM pasa al estado "todo lo que se desea".

Si se encuentra en el estado "entero iniciado" y cumple ".", FSM pasa al estado "flotante o lista de enteros iniciada" y así sucesivamente.


Usted tiene respuestas y comentarios que sugieren el uso de una máquina de estados, y yo lo apoyo completamente. Desde el código que muestra en Edit1, veo que todavía no implementó una máquina de estado. Por los comentarios, creo que no sabes cómo hacerlo, así que para empujarte en esa dirección, aquí hay un enfoque:

Defina los estados con los que necesita trabajar:

type TReadState = (ReadingIdle, ReadingText, ReadingInt, ReadingFloat); // ReadingIdle, initial state or if no other state applies // ReadingText, needed to deal with strings that includes digits (P7..) // ReadingInt, state that collects the characters that form an integer // ReadingFloat, state that collects characters that form a float

Luego define el esqueleto de tu máquina. Para mantenerlo lo más fácil posible, opté por utilizar un enfoque de procedimiento directo, con un procedimiento principal y cuatro subprocedimientos, uno para cada estado.

procedure ParseString(const s: string; strings: TStrings); var ix: integer; ch: Char; len: integer; str, // to collect characters which form a value res: string; // holds a final value if not empty State: TReadState; // subprocedures, one for each state procedure DoReadingIdle(ch: char; var str, res: string); procedure DoReadingText(ch: char; var str, res: string); procedure DoReadingInt(ch: char; var str, res: string); procedure DoReadingFloat(ch: char; var str, res: string); begin State := ReadingIdle; len := Length(s); res := ''''; str := ''''; ix := 1; repeat ch := s[ix]; case State of ReadingIdle: DoReadingIdle(ch, str, res); ReadingText: DoReadingText(ch, str, res); ReadingInt: DoReadingInt(ch, str, res); ReadingFloat: DoReadingFloat(ch, str, res); end; if res <> '''' then begin strings.Add(res); res := ''''; end; inc(ix); until ix > len; // if State is either ReadingInt or ReadingFloat, the input string // ended with a digit as final character of an integer, resp. float, // and we have a pending value to add to the list case State of ReadingInt: strings.Add(str + '' (integer)''); ReadingFloat: strings.Add(str + '' (float)''); end; end;

Ese es el esqueleto. La lógica principal está en los cuatro procedimientos estatales.

procedure DoReadingIdle(ch: char; var str, res: string); begin case ch of ''0''..''9'': begin str := ch; State := ReadingInt; end; '' '',''.'': begin str := ''''; // no state change end else begin str := ch; State := ReadingText; end; end; end; procedure DoReadingText(ch: char; var str, res: string); begin case ch of '' '',''.'': begin // terminates ReadingText state str := ''''; State := ReadingIdle; end else begin str := str + ch; // no state change end; end; end; procedure DoReadingInt(ch: char; var str, res: string); begin case ch of ''0''..''9'': begin str := str + ch; end; ''.'': begin // ok, seems we are reading a float str := str + ch; State := ReadingFloat; // change state end; '' '','','': begin // end of int reading, set res res := str + '' (integer)''; str := ''''; State := ReadingIdle; end; end; end; procedure DoReadingFloat(ch: char; var str, res: string); begin case ch of ''0''..''9'': begin str := str + ch; end; '' '',''.'','','': begin // end of float reading, set res res := str + '' (float)''; str := ''''; State := ReadingIdle; end; end; end;

Los procedimientos estatales deben ser autoexplicativos. Pero solo pregunta si algo no está claro.

Las dos cadenas de prueba dan como resultado los valores enumerados según lo especificado. Una de sus reglas era un poco ambigua y mi interpretación podría ser incorrecta.

los números no pueden ser precedidos por una letra

El ejemplo que proporcionó es "P7", y en su código solo verificó el carácter anterior inmediato. Pero, ¿y si se lee "P71"? Lo interpreté como que "1" debería omitirse como "7", aunque el carácter anterior de "1" es "7". Esta es la razón principal del estado de ReadingText de ReadingText , que termina solo en un espacio o período.