BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
sskt
Obsidian | Level 7

お世話になります。

textの列の中で半角の数字が3つ以上並んだ部分のみを全角に変換したいと考えており、以下サイトを参考に半角数字が3つ並んだ部分の有無については検出に成功しました。

ですが、置換のやり方がわからないです。

http://tetchi-kun.hatenablog.com/entry/2015/01/13/183205

 

PRXCHANGE関数で行けるかと思い、色々試していますがうまくいきません。

 

よろしくお願いいたします。

1 ACCEPTED SOLUTION

Accepted Solutions
japelin
Rhodochrosite | Level 12

そこだけ

          if cnt>=3 then do;
            modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'));
          end; else
          do;
            modified=cats(modified,temp);
          end;

と修正すればいいかと思います。

View solution in original post

11 REPLIES 11
japelin
Rhodochrosite | Level 12

PRXCHANGEは使ったことがないのですが、マッチした検索文字列をTRANWRDで置換すればいいと思いますがいかがでしょうか。

 

DATA _null_;
  length modified $200;/* 全角にするのでoriginalの2倍確保したい */
  original = '東京都品川区広町2丁目1-306-F111棟505号室';
  modified = original;
  /* 正規表現識別子 */
  prxid=PRXPARSE('/[0-9]{3,}/');
  idx = PRXMATCH(prxid, modified);
  do while(idx>0);
    before = PRXPOSN(prxid, 0, modified);
    alter  = KPROPCASE(before,'HALF-ALPHABET, FULL-ALPHABET');
    modified = TRANWRD(modified,trim(before),trim(alter));
    putlog modified=;
    idx = PRXMATCH(prxid, modified);
  end;
RUN;

 

sskt
Obsidian | Level 7
無事実行できることを確認しました。ありがとうございます。

ただ、文字も長くオブザーベーション数も多い(4000バイト、10000サンプル以上、1変数のみ)からか、処理が終わる気配がないです。
(かなりハイスペックなPCで実行しましたが、半日たっても終わる気配がないです。)

また、少しやり方を変えており、originalとmodifiedを分けずにそのまま上書く形にしています。

どうすれば処理を現実的な時間で終わらせられるでしょうか?
アドバイスいただけますと幸いです。
japelin
Rhodochrosite | Level 12

 

流石にそのデータ量でも半日はかかりすぎな気もするので、データセットオプションで(obs=2)くらいを指定して

処理がループしていないかどうかを確認されてはいかがでしょうか。

(10秒くらいで終わらなければ処理を中断してログを確認する)

 

また、先のプログラムに誤りがありました。

TRANWRDを使うとヒットする文字すべてを置き換えてしまうため、

「ABC222DEF22222」

のような場合、最初に「222」が変換キーワードになり

「ABC222DEF22222

と後ろの「22」が変換されずに残ってしまいます。

 

ちょっと古典的ではありますが、substr(subpad)で文字列を再構築する方法でやってみました。

 

/* サンプルデータ作成 */
data sample(keep=original modified);
  length original $4000 /* 4000バイト */
         modified $8000 /* 全角変換するので倍 */
         rand0 8        /* 数値、アルファベットの処理フラグ */
         rand1 8        /* 連続させる文字数 */
         rand2 8;       /* ランダムな英数字 */
  modified='';
  seed=int(datetime());
  do i=1 to 20000;
    original='';
    do until(length(original)>3999);
      rand0=mod(int(100*ranuni(seed)+1),4);
      rand1=int(10*ranuni(seed)+0);
      select (rand0);
        when(1) rand2=int( 9*ranuni(seed)+48);
        when(2) rand2=int(25*ranuni(seed)+65);
        when(3) rand2=int(25*ranuni(seed)+97);
        otherwise;
      end;
      original=cats(original,repeat(byte(rand2),rand1));
    end;    
    output;
  end;
run;

/* 変換する */
data converted(keep=original modified);
  set sample;
  modified = original;
  /* 正規表現識別子 */
  prxid=PRXPARSE('/[0-9]{3,}/');
  idx = PRXMATCH(prxid, modified);
  do while(idx>0);
    before  = PRXPOSN(prxid, 0, modified);
    after   = KPROPCASE(before,'HALF-ALPHABET, FULL-ALPHABET');
    modified= subpad(modified,1,index(modified,strip(before))-1)||trim(after)||substr(modified,index(modified,strip(before))+length(before));
    idx     = PRXMATCH(prxid, modified);
  end;
run;

 

20000obs、4000バイトでも40秒程度で処理が終わりました。

 

sskt
Obsidian | Level 7

コードの書き換えありがとうございます。確認させていただきます。

 

もう一度回しましたが、やはり追加したコードのところで動かなくなります。

試しにobs=2にしたら、上から2サンプルのテキストが空だったので最後まで回りましたが、obs=200にした場合、文字が必ず入ってくるので今までと同じところで止まりました。

ちなみにデータセットの上から20サンプルはテキスト部分が空であり、21サンプル目からがテキストが入っているようですが、強制終了したところ21サンプルで停止しました、とのWARNINGがでるのでやはり原因は追加したコードのようです。

 

ちなみに今回は全角から半角の変換です。

'/[0-9]{3,}/'とし、FULLとHALFを修正しているので問題はないと思われます。

 

なにか他に原因が疑われるところがありましたら、ご助言いただけますと幸いです。

 

japelin
Rhodochrosite | Level 12


最初は

 

textの列の中で半角の数字が3つ以上並んだ部分のみを全角に変換したいと考えており、


とおっしゃっていましたので従前のコードをご提示しましたが、

 


ちなみに今回は全角から半角の変換です。

ということだと話が変わります。このコードでは動きません。

PRXPOSN関数もPRXMATCH関数もDBCSには対応しておらず、全角文字を検出できないためです。

 

思いつくのは、kindexcで0-9を検出して、検出した文字以降を

1文字ずつチェックして3桁以上の場合は変換、という感じでしょうか。

ロジックが面倒かつ処理時間も掛かりそうですが…

 

japelin
Rhodochrosite | Level 12
    length temp $4000
           char $2;  
do i=1 to klength(original); char=ksubstr(original,i,1); if char in ('0','1','2','3','4','5','6','7','8','9') then do; cnt=cnt+1; temp=cats(temp,char); if i=klength(original) then do; if cnt>=3 then do; modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char); end; else do; modified=cats(modified,char); end; end; end; else if cnt>=3 then do; modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char); cnt=0; temp=''; end; else if 0<cnt<3 then do; modified=cats(modified,temp,char); cnt=0; temp=''; end; else do; modified=cats(modified,char); end; end;

10分くらいはかかると思いますが参考まで。

doループの開始をkindexcで調べた値にすると少し改善できると思います。

 

sskt
Obsidian | Level 7

ありがとうございます。拝見いたしました。

 

頂いたコードを少しいじって試してみたのですが、うまく行かなかったです。。。

 

DATA a;
length original $100 modified $200;/* 全角にするのでoriginalの2倍確保したい */
original = '東京都品川区広町2丁目1-306-F11棟5050号室';
modified = original;
run;data b;
set a;
/* 正規表現識別子 */
length temp $4000
char $2;
do i=1 to klength(original);
char=ksubstr(original,i,1);
if char in ('0','1','2','3','4','5','6','7','8','9') then do;
cnt=cnt+1;
temp=cats(temp,char);
if i=klength(original) then do;
if cnt>=3 then do;
modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char);
end; else
do;
modified=cats(modified,char);
end;
end;
end; else
if cnt>=3 then do;
modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char);
cnt=0;
temp='';
end; else
if 0<cnt<3 then do;
modified=cats(modified,temp,char);
cnt=0;
temp='';
end; else
do;
modified=cats(modified,char);
end;
end;
RUN;

 

実行後データセットを開けてみると、cntがブランクになっているのが気になりますが問題はないでしょうか。

 

また、そもそもロジックとして全角→半角の前に、半角文字を全角にしてその後3桁以上のものを全角に戻しているのですが、その際に3桁以上のものは全角に変換しない、というロジックのほうが楽だったりしますでしょうか。

japelin
Rhodochrosite | Level 12

以下にサンプル作成から変換までフルコード載せています。(OBS数は200に制限しています)

これをこのまま実行して、想定通りか確認してみてください。

その後、ご自身のデータをsetして動くか確認してください。

 

/* サンプルデータ作成 */
data sample2(keep=original);
  array zen{36} $2 ('a','b','c','d','e','f','g','h','i','j'
                   ,'k','l','m','n','o','p','q','r','s','t'
                   ,'u','v','w','x','y','z'
                   ,'1','2','3','4','5','6','7','8','9','0'
                   );

  length original $4000 /* 4000バイト */
         rand0 8        /* 数値、アルファベットの処理フラグ */
         rand1 8        /* 連続させる文字数 */
         rand2 $2;      /* ランダムな英数字 */
  seed=int(datetime());
  do i=1 to 20000;
    original='';
    do until(length(original)>3999);
      rand0=mod(int(100*ranuni(seed)+1),4);
      rand1=int(10*ranuni(seed)+0);
      select (rand0);
        when(0) rand2=zen {int(35*ranuni(seed)+ 1)};
        when(1) rand2=byte(int( 9*ranuni(seed)+48));
        when(2) rand2=byte(int(25*ranuni(seed)+65));
        when(3) rand2=byte(int(25*ranuni(seed)+97));
        otherwise;
      end;
      original=cats(original,repeat(strip(rand2),rand1));
    end;    
    output;
  end;
run;

/* 半角(数字3桁以上)から全角に変換 */
data H2Z_converted(keep=original modified);
  set sample2(obs=100);
  length modified $8000;
  modified = original;
  /* 正規表現識別子 */
  prxid=PRXPARSE('/[0-9]{3,}/');
  idx = PRXMATCH(prxid, trim(modified));
  do while(idx>0);
    before  = PRXPOSN(prxid, 0, modified);
    after   = KPROPCASE(before,'HALF-ALPHABET, FULL-ALPHABET');
    modified= subpad(modified,1,index(modified,strip(before))-1)||trim(after)||substr(modified,index(modified,strip(before))+length(before));
    idx     = PRXMATCH(prxid, trim(modified));
  end;
run;


/* 全角(数字3桁以上)から半角に変換 */
data Z2H_converted(keep=original modified);
  set sample2(obs=100);
  length modified $4000 
         temp $4000  /* 全角文字列 */
         char $2;    /* 処理対象文字 */
  idx=kindexc(original,'0123456789');
  cnt=0;
  temp='';
  if idx=0 then do;
    modified=original;
  end; else
  do;
    if idx=1 then do;
      modified='';
    end; else
    if idx>1 then do;
      modified=ksubstr(original,1,idx-1);
    end;
    do i=idx to klength(original);
      /* 全角数値ならキープ */
      char=ksubstr(original,i,1);
      if char in ('0','1','2','3','4','5','6','7','8','9') then do;
        cnt=cnt+1;
        temp=cats(temp,char);
        /* 最後だけ全角数値でも処理を行う */
        if i=klength(original) then do;
          if cnt>=3 then do;
            modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char);
          end; else
          do;
            modified=cats(modified,char);
          end;
        end;
      end; else
      /* 全角数値でない、3連続以上全角数値だったら置換 */
      if cnt>=3 then do;
        modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'),char);
        cnt=0;
        temp='';
      end; else
      /* 全角数値でない、3連続未満全角数値だったらそのまま */
      if 0<cnt<3 then do;
        modified=cats(modified,temp,char);
        cnt=0;
        temp='';
      end; else
      /* 全角数値でない、直前が全角数値以外だったらそのまま */
      do;
        modified=cats(modified,char);
      end;
    end;
  end;
run;

 

sskt
Obsidian | Level 7

プログラムありがとうございます。
こちらの説明があいまいで申し訳ございません。コメントまでつけてくださり、非常に助かります。

 

一点気になったのですが、
/* 全角(数字3桁以上)から半角に変換 */
の部分ですが、文字列の最後が変換後、半角数字で終わってしまうものについて、0830→08300のようになってしまいます。

/* 最後だけ全角数値でも処理を行う */の部分で対応されているのでしょうか?(うまく行っていない?)

 

どうぞよろしくお願いいたします。

japelin
Rhodochrosite | Level 12

そこだけ

          if cnt>=3 then do;
            modified=cats(modified,KPROPCASE(temp,'FULL-ALPHABET, HALF-ALPHABET'));
          end; else
          do;
            modified=cats(modified,temp);
          end;

と修正すればいいかと思います。

izumi_sas
SAS Employee

他の回答と同じことを言っているかもしれませんが。ご参考まで。

 

PRXCHANGEや、PRXMATCH関数を使わなくなって久しいので、すぐに試して確認した結果をお伝え出来ず恐縮ですが、これらSASの正規表現の関数を使う場合、引数上でTRIMなどで後ろの空白を削除するようにすることでパフォーマンス向上が期待できたと記憶しています。

例)RC = PRXCHANGE(prxid, trim(modified))

 

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!
Discussion stats
  • 11 replies
  • 3510 views
  • 8 likes
  • 3 in conversation