詰めSAS10回目_変数内の1文字ごとにオブザベーションを起こす

この問題はまだ納得できる最適解がでていません。お力を貸してください。
また、1手では無理だと思います。

今、以下のようなデータセットがあったとします。

data Q1;
X='ABCDE';output;
X='12';output;
X='WXYZ';output;
run;








ここから以下のようなデータセットを作りたいとします










一文字ごとにobsになりX_○の、○の部分は元のデータセットのオブザベーション番号です。

問題は3obsですが、何オブザベーションになったとしても動き、かつ対象変数Xの変数の長さが
いくつになっても動くプログラムを書くことが問題となります。
ただし、めんどうなので今回はXの中には半角英数字(1byte文字)しか入らないという前提条件をつけます。


【解法1:悪手】

最初に思いついたのは以下の一連の流れです


/*オブザベーション数と対象変数のlengthをマクロ変数に代入*/
data _NULL_;
  set Q1 nobs=tobs;
  call symputx('obs',tobs);
  call symputx('len',length(X));
 stop;
run;

/*変数内の一文字ごとにobsを起こすマクロ*/
%macro separate(po);
data A&po;
  PT=&po;
 set Q1 point=PT;
   do i=1 to &len;
     X_&po=char(X,i);
output;
   end;
 stop;
 run;
%mend;

/*separateマクロの対象を元DSの1obsずつ最後までずらすマクロ
  ついでにループ変数でマージして完成まで*/
%macro loop;
 %do i=1 %to &obs;
  %separate(&i)
 %end;
 data OUTPUT;
  merge A:;
  by i;
  drop X i;
 run;
%mend;

/*実行*/
%loop


ようは1obsごとに、変数内の1文字ごとに1obsとなるデータセットをそれぞれ作り


























それをマージで連結するという発想でした。

局面を細かく区切ってマクロループに頼るのは大局感を欠いた悪手になりがちですが
今回もそんな感じがしますね。


【解放2:まだマシか】

data _NULL_;
  set Q1;
  call symputx('len',length(X));
  stop;
run;

data AA1;
 set Q1;
  array AX(&len) $1;
  do i=1 to &len;
   AX(i)=char(X,i);
  end;
  IDNO=_N_;
  drop i;
run;

proc transpose data=AA1 out=OUTPUT2(drop=_NAME_) prefix=X_;
 var AX:;
 id IDNO;
run;

先に変数内の文字区切りから横に展開して変数を作ります。







↑データセット[AA1]の中身

これを転置して完成です。

オブザベーション数を取得していないので、1手得してます。
また1データステップ内で横にループはしていますが、データステップ自体をループしていないので
先の方法よりかはずいぶん効率がいいです。


でも、あんまり美しくないので、もっといい答えがあれば教えてください。


実践的な問題ではない??
詰め将棋だってそんなもんです。
たとえ非現実的な局面の問題でも、解けば棋力の底上げになるはずです。




3 件のコメント:

  1. こんにちは。
    この問題結構好きで、時間があるとき解法を考えてます。

    悪手ですが、2手までは短縮できました。
    結局ループ数が多くなってしまい意味がないんですが。。

    今のところ解法2が一番きれいですね。
    なんか1手で出来そうな感じはしてるので、また引き続きこの問題を楽しみたいと思います。

    data DT1;
    do ID1 = 1 to 1000 until (Y="");
    do ID2 = 1 to _OBS;

    set Q1 nobs=_OBS point=ID2;
    Y = char(X,ID1);
    output;

    end;
    end;
    stop;
    run;

    proc transpose data=DT1 out=DT2(drop=ID1 _NAME_) prefix=X_;
    var Y;
    by ID1;
    run;

    返信削除
  2. これ、挑戦してくれていたなんて、とても嬉しいです!

    おぉ!Setをdoループで包むやり方で来ましたか!!コンパクトです!
    1000にしているのは多分コードをわかりやすくするためですよね

    data DT1;
    do until(Y="");
    ID1+1;
    do ID2 = 1 to _OBS;
    set Q1 nobs=_OBS point=ID2;
    Y = char(X,ID1);
    output;
    end;
    end;
    stop;
    run;

    とすれば、多分変数に格納できる最大の数まで文字がみっちりつまっていても動きますよね?

    ご指摘の通り、あとはtransposeの部分をデータステップの中にぶちこむことができれば1手にできそうですね、、。

    他のアプローチもありそうですし、楽しいです。

    返信削除
  3. あ、そうかID1+1を置くだけでよかったですね。
    普通に気付かなかったです!

    マクロとか使えば1ステップでいけそうですが、
    シンプルにデータステップだけで解決しようとすると、
    arrayの問題にぶち当たります。

    「array X_(_OBS) $;」と指定できれば、解決してしまうんですが。。
    これは将来のバージョンで対応してほしいところです。

    data DT1;
    array X_(100) $; * 修正必要あり① ;
    do until(Y="");
    ID1+1;
    do ID2 = 1 to _OBS;

    set Q1 nobs=_OBS point=ID2;
    Y = char(X,ID1);
    X_(ID2) = Y; * 修正必要あり② ;

    end;
    output;
    end;
    stop;

    keep X_1--X_3; * 修正必要あり③ ;
    run;

    返信削除