文字と数字を分解するには

解決


Taku  2002-09-22 04:55:13  No: 1549

最近、文字リストの高速化についての質問がありますが
その関連でお願いします
StringListにA〜Zの文字と小数点付数字が不規則に入っています
A1.23B-45.6C1000
X0Y0.005B-789.123
のような感じです。
リストの一行毎の処理の中で
各アルファベットに対する数値データの値を代入したいのですが

var Adress:array[0..26] of String;
    SS,S:String;    //Sはリスト1行分
    i,Item:Byte;

For i := 1 To Length(S) do
begin
     SS := copy(S, i,1);  
     if SS='A' then Item=1  〜  SS='Z' then Item=26    
     else Item=0;
     if Item=0 then Adress[Item]:=Adress[Item]+SS
end;
      
     A:=strtofloat(Adress[1]); 〜  
     Z:=strtofloat(Adress[26]); 
    
のように、1文字毎に調べて変数に加えていく方法で処理しています。
1行にある文字数は最大でも30程度ですが、行数は数十万になる事もあり
出来れば高速に処理したいので、良い方法をお教えください。


にしの  2002-09-22 19:56:41  No: 1550

必ず、アルファベットと数値の順序であるのであれば、アルファベットの位置を探して、その間を数値として取得します。
それと、変数ごとに切り分けるのも、TStringList.Valuesで代用し、使用するときにStringList.Values['A']というようにします。

それを、次のようなプロシージャに投げてやれば入ります。
ただし、逆順序です^^;
# LastDelimiterを使用しているため

ざっと作ったので、もう少し高速化の余地はあると思います。

procedure ListFromString(lst: TStringList; Text: String);
const
  C_ALPHA = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
var
  i, j: Integer;
  ParamName, ParamValue: String;
begin
  j := StrLen(PChar(Text));
  i := LastDelimiter(C_ALPHA, Text);
  while i > 0 do
  begin
    ParamName  := Copy(Text, i, 1);
    ParamValue := Copy(Text, i+1, j - i);
    Lst.Values[UpperCase(ParamName)] := ParamValue;
    Text := Copy(Text, 1, i - 1);
    j := StrLen(PChar(Text));
    i := LastDelimiter(C_ALPHA, Text);
  end;
end;


hatena  2002-09-23 03:07:31  No: 1551

このデータ構造なら一文字ずつスキャンするのは仕方がないですが、
Takuさんのコードで、

>  SS := copy(S, i,1);  

とcopyで一文字ずつ切り分けてますが、SS := S[i] とインデックスで
アクセスした方が高速です。

>     if SS='A' then Item=1  〜  SS='Z' then Item=26    

if で何回もチェックするのも効率が悪いです。
SS をChar型にすれば、case文が使えて高速。

>var Adress:array[0..26] of String;

    Adress:array['A'..'Z'] of String;
とインデックスをChar型にして、文字で直接アクセスできるように
すると効率がよさそう。

とういようなことを考慮して、

var
    Adress:array['A'..'Z'] of String; 

  procedure ArrayFromString(S: String);
  var
    SS, Item: Char;
    i, j:integer;
  begin
    Item := S[1];
{    case Item of 'a'..'z': Item := Chr(Ord(Item)-32); end;}
    j := 2;
    SS := S[j];
    for i := 2 to Length(S) do
    begin
      SS := S[i];
      case SS of
        'A'..'Z': begin
            Adress[Item] := Copy(S, j, i-j);
            Item := SS;
            j := i+1;
          end;
{        'a'..'z': begin
            Values[Item] := StrToFloat(Copy(S, j, i-j));
            Item := Chr(Ord(SS)-32);
            j := i+1;} 
          end;
      end;
    end;
    Adress[Item] := Copy(S, j, Length(S) - j);
  end;

※コメントの{}を外すと小文字にも対応。

呼び出すときは、
A := strtofloa(Adress['A']);

とします。

サンプルを作って、
にしのさんの方法と比較してみましたが、9倍ほど高速でした。

データ構造を、工夫すればもっと目に見えて速くすることは
できるんですが。。。


Taku  2002-09-23 09:15:26  No: 1552

にしのさん、hatenaさん  有難うございます
hatenaさんの方法が今のに近いので、早速に書き換えてみましたが
他の条件があるのを忘れていました
A123A456A789 <-  同じアルファベットが複数ある
/X123        <-  /  *  等の演算子がある 
B123(COMENT) <-  数値があるとは限らない  

Aが複数ある場合は今でも別の配列に入れていますので問題ないのですが
先頭に  /  *  等が含まれると範囲外エラーになったり
A〜Zの後にある場合は数値に代入する際エラーになります

これまでは後の処理で、
for i:=0 to 26 do
   if Adress[i]<>'' then xxxxx
の所を
if Adress['A']<>'' then xxxxx; 〜  if Adress['Z']<>'' then xxxxx;
の処理に変更したり、関数計算の後、グラフィック表示したりで全体の高速化には
なりませんが、実際に扱うデータで確実に早くなっているのが確認できました

/  *  の問題が解決すれば使えそうなので
array['A'..'Z']で  '/'  '*'  '('  を扱う方法をお願いします


hatena  2002-09-23 20:43:37  No: 1553

>A123A456A789 <-  同じアルファベットが複数ある

>Aが複数ある場合は今でも別の配列に入れていますので問題ないのですが

配列が複数用意してあるのですか。

>/X123        <-  /  *  等の演算子がある 

演算子が合った場合はどうするのでしょう。

>B123(COMENT) <-  数値があるとは限らない  

()の部分はコメントして無視すると言うことでしょうか。

その他、配列に入れた後、どのように利用するかなど、ふくめて細かい仕様が
分からないので、なんなんですが、

'A'..'Z'があったら、それ以降の数値は読み込む。
()の間は、読み飛ばす。
それ以外の文字は読み飛ばすという仕様とすると、下記の例になります。

細かい制御が必要なので、Forで回すのはやめて、Whileで回して、
見通しをよくするためにCase文内はサブルーチンにしました。

  procedure ArrayFromString;
  var
    SS, Item: Char;
    i, j:integer;

    procedure GetValue;
    begin
      Item := SS;
      inc(i);
      SS := S[i];
      j := i;
      if SS = '-' then Inc(i);
      while SS in ['.','0'..'9'] do
      begin
        Inc(i);
        SS := S[i];
      end;
      Adress[Item] := Copy(S, j + 1, i-j);
    end;

    procedure SkipComment;
    begin
      Repeat
        inc(i);
        SS := S[i];
      until (SS = ')') or (SS=#0);
    end;

  begin
    i := 1;
    j := 1;
    SS := S[1];
    while true do
    begin
      case SS of
        #0: begin
             Adress[Item] := Copy(S, j, i-j);
             Break;
          end;
        '(': SkipComment;
        'A'..'Z': GetValue;
      else
        Inc(i);
        SS := S[i];
      end;
    end;
  end;


hatena  2002-09-23 20:50:08  No: 1554

上記の訂正です。

>    procedure GetValue;
>    begin
>      Item := SS;
>      inc(i);
>      SS := S[i];
>      j := i;
>      if SS = '-' then Inc(i);

上の一行を削除して、下記で置き換えて下さい。
      if SS = '-' then
      begin
        Inc(i);
        SS := S[i];
      end;


Taku  2002-09-24 00:11:12  No: 1555

hatenaさま  有難うございます

余分なことを書いてしまったようですが、演算子や括弧は無視できます

初めの所の、Item := S[1];  で先頭に/が有った場合
Adress[Item] := Copy(S, j, i-j);  の所でエラーになってしまいます
今回のレスを見て
その前に単純に、Itemを検査する事でエラーは回避できる事に気が付きました。

今回の部分の改善で多少ではありますがスピードアップすることが出来ました
拾った数値は、条件分岐データ(整数)、数値データ(浮動小数点)等が
混同しており、以降で各処理をして行きます。
その部分でも、今回頂いたアドバイスが利用出来そうです
大変参考になりました  有難うございました


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加