开发者

Something like StrTok() or Sscanf()?

开发者 https://www.devze.com 2023-02-09 23:25 出处:网络
So, I am reading from开发者_开发百科 ModBos over serial port and get readings something like the following : \'+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003\';

So, I am reading from开发者_开发百科 ModBos over serial port and get readings something like the following : '+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003';

Basically, there will always be 8 floating point readings, preceded by a plus or minus sign, although they may be of varying character length.

What's the most efficient way to get the values an array of float (or array of string or TSringList)?

I am not certain, but this might be time critical, so efficiency probably has way over elegance.


I would do something like this:

type
  TFloatArray = array[0..7] of Double;

procedure ParseFloats(const aFloatStr: string;
  var aFloatArray: TFloatArray);
var
  lPos: Integer;
  lNextPos: Integer;
  lPosPositive: Integer;
  lPosNegative: Integer;
  i: Integer;
  lFormatSettings: TFormatSettings;
begin
  //do not forget formatsettings, or you will get problems with regional settings
  lFormatSettings.DecimalSeparator := '.';
  lFormatSettings.ThousandSeparator := ',';
  lPos := 1;
  for i := 0 to High(aFloatArray) do
  begin
    lPosPositive := PosEx('+', aFloatStr, lPos + 1);
    lPosNegative := PosEx('-', aFloatStr, lPos + 1);
    if lPosPositive = 0 then
      lNextPos := lPosNegative
    else if lPosNegative = 0 then
      lNextPos := lPosPositive
    else
      lNextPos := Min(lPosPositive, lPosNegative);
    if lNextPos = 0 then
      lNextPos := Length(aFloatStr) + 1;
    aFloatArray[i] := StrToFloat(Copy(aFloatStr, lPos, lNextPos - lPos), lFormatSettings);
    lPos := lNextPos;
  end;
end;

//call like this
var
  lFloats: TFloatArray;
begin
  ParseFloats('+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003', lFloats);
end;

Because there are always 8 float values, a fixed array of 8 doubles is enough. I kept string manipulation to a minimum, only once per floating point value a string is copied. Important is the TFormatSettings, otherwise you will get errors on systems where the decimal separator is not a dot (like mine).

There is no exception handling here, I expect a string with 8 floating point values, nothing more, nothing less.


You can download and use VC++ sscanf ported to Delphi.


you can use the TParser class to parse your string.

check this sample application

program ParserDemo;

{$APPTYPE CONSOLE}

uses
  Classes,
  SysUtils;

procedure ProcessModBosOutPut(OutPut : string);
var
 StringStream  : TStringStream;
 Parser        : TParser;
 dValue        : Double;
 sValue        : string;
 FormatSettings: TFormatSettings;
begin
   FormatSettings.DecimalSeparator :='.';
   FormatSettings.ThousandSeparator:=',';
   //transform the output string to fit with the TParser logic
   OutPut:=StringReplace(OutPut,'+',' ',[rfReplaceAll]); //replace  '+' sign with a space
   OutPut:=StringReplace(OutPut,'-',' -',[rfReplaceAll]); //insert a empty space after of a '-' sign

   StringStream:=TStringStream.Create(OutPut);
   Parser:=TParser.Create(StringStream);
   try
        while Parser.Token <> toEOF do
        begin
           sValue:=Parser.TokenString; //get the string 
           dValue:=StrToFloat(sValue,FormatSettings); //convert the string
           //do something with the float value
           Writeln(FloatToStr(dValue));
           Parser.NextToken;
        end;
   finally
     Parser.Free;
     StringStream.Free;
   end;
end;

begin
  try
    ProcessModBosOutPut('+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003');
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  Readln;
end.


program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils;

const
  CString = '+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003';


var
  i,idx: Integer;
  tmpArray: Array[0..7] of Double;
  tmpString: ShortString;

begin
  DecimalSeparator := '.';

  idx := Low(tmpArray);
  tmpString := '';



  tmpString := CString[1];
  for i := 2 to Length(CString) do
  begin
    if CString[i] in ['+', '-']
    then begin
      TryStrToFloat(tmpString, tmpArray[idx]);
      Inc(idx);
      tmpString := CString[i];
    end
    else begin
      tmpString := tmpString + CString[i];
    end;
  end;
  TryStrToFloat(tmpString, tmpArray[idx]);


  for i := Low(tmpArray) to High(tmpArray) do
  begin
    Writeln(FloatToStr(tmpArray[i]));
  end;


  ReadLn;
end.


For those interested in repeating the performance tests, following can be copied and pasted in a new console project using Delphi XE.

program Project1;

uses
  classes,
  sysutils,
  strutils,
  math;

{$APPTYPE CONSOLE}

type
  TFloatArray = array[0..7] of Double;

procedure ParseFloats_TheFox(const aFloatStr: string;
  var aFloatArray: TFloatArray);
var
  lPos: Integer;
  lNextPos: Integer;
  lPosPositive: Integer;
  lPosNegative: Integer;
  i: Integer;
  lFormatSettings: TFormatSettings;
begin
  //do not forget formatsettings, or you will get problems with regional settings
  lFormatSettings.DecimalSeparator := '.';
  lFormatSettings.ThousandSeparator := ',';
  lPos := 1;
  for i := 0 to High(aFloatArray) do
  begin
    lPosPositive := PosEx('+', aFloatStr, lPos + 1);
    lPosNegative := PosEx('-', aFloatStr, lPos + 1);
    if lPosPositive = 0 then
      lNextPos := lPosNegative
    else if lPosNegative = 0 then
      lNextPos := lPosPositive
    else
      lNextPos := Min(lPosPositive, lPosNegative);
    if lNextPos = 0 then
      lNextPos := Length(aFloatStr) + 1;
    //aFloatArray[i] := StrToFloat(Copy(aFloatStr, lPos, lNextPos - lPos), lFormatSettings);
    WriteLn(StrToFloat(Copy(aFloatStr, lPos, lNextPos - lPos), lFormatSettings));
    lPos := lNextPos;
  end;
end;

procedure ProcessModBosOutPut_RRUZ(OutPut : string);
var
 StringStream  : TStringStream;
 Parser        : TParser;
 dValue        : Double;
 sValue        : string;
 FormatSettings: TFormatSettings;
begin
   FormatSettings.DecimalSeparator :='.';
   FormatSettings.ThousandSeparator:=',';
   //transform the output string to fit with the TParser logic
   OutPut:=StringReplace(OutPut,'+',' ',[rfReplaceAll]); //replace  '+' sign with a space
   OutPut:=StringReplace(OutPut,'-',' -',[rfReplaceAll]); //insert a empty space after of a '-' sign

   StringStream:=TStringStream.Create(OutPut);
   Parser:=TParser.Create(StringStream);
   try
        while Parser.Token <> toEOF do
        begin
           sValue:=Parser.TokenString; //get the string
           dValue:=StrToFloat(sValue,FormatSettings); //convert the string
           //do something with the float value
           Writeln(FloatToStr(dValue));
           Parser.NextToken;
        end;
   finally
     Parser.Free;
     StringStream.Free;
   end;
end;


procedure Jorn(const floatstring: string);
var
  i,idx: Integer;
  tmpArray: Array[0..7] of Double;
  tmpString: ShortString;
begin
  DecimalSeparator := '.';

  idx := Low(tmpArray);
  tmpString := '';

  tmpString := floatstring[1];
  for i := 2 to Length(floatstring) do
  begin
    if floatstring[i] in ['+', '-']
    then begin
      writeln(strtofloat(tmpString));
      //TryStrToFloat(tmpString, tmpArray[idx]);
      Inc(idx);
      tmpString := floatstring[i];
    end
    else begin
      tmpString := tmpString + floatstring[i];
    end;
  end;
  //TryStrToFloat(tmpString, tmpArray[idx]);
  writeln(strtofloat(tmpString));
end;

//call like this
var
  lFloats: TFloatArray;
  I: Integer;
begin
  for I := 0 to 999 do
  begin
    ParseFloats_TheFox      ('+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003', lFloats);
    WriteLn('The Fox');

    ProcessModBosOutPut_RRUZ('+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003');
    WriteLn('RRUZ');

    Jorn                    ('+0020.8+0022.8-00.046-00.002-00.005-001.99+00.000+00.003');
    WriteLn('Jorn');
  end;

  readln;
end.
0

精彩评论

暂无评论...
验证码 换一张
取 消