Proc Luaの世界②-自由奔放なLuaのテーブルの話

さて、Luaの文法的な話ばかりだと面白くないので、今回の記事で紹介する機能を使ってできる小技を先に紹介します。

他の方のブログでも何度か言及されていますがSASマクロの%doループはちょっと使いにくいところがあります。

[データ解析備忘録]
アルファベットや飛び飛びの値でもループしたい

たとえばSASHELPライブラリにあるCLASS FISH CARSの3つのデータセットをループ処理で
順番にproc printにかけたいなと思えば

%macro mloop;
 %let loopval=CLASS FISH CARS;
 %do i=1 %to %sysfunc(countw(&loopval));
  %let target=%scan(&loopval,&i);
      proc print data=sashelp.⌖run;
 %end;
%mend;
%mloop;

とかけます。
はい、わかりにくいですね(別にSASマクロをディスってるわけじゃないですよ!)

proc luaの場合、色々と書けるんですが、とっつきやすい1パターン紹介です。

proc lua;
submit;
local target = {'CLASS','FISH','CARS'}
for i, item in ipairs(target) do
sas.submit("proc print data=sashelp."..item..";run")
end
endsubmit;
run;

と、これでOKです。うん、発想的にも見かけ的にもcall executeと同じなので、何やってるかはわかりますね。

上のように実行コードが単純な1行程度なら文字列連結でもいいですけど、複雑になってくると
手に負えないので、キーワードパラメータもちのマクロのように、パラメータを分離して以下のように書くほうが綺麗かな

proc lua;
submit;
local target = {'CLASS','FISH','CARS'}
local code =
[[
proc print data=sashelp.@dataset@;
run;
]]
for i, item in ipairs(target) do
sas.submit(code, { dataset = item } ) --ここでパラメータ指定して実行
end
endsubmit;
quit;

うん、発想はマクロと同じですが、こっちの方が美しいと僕は思いますけどね。
まあ、Luaの中でSASコードを生成して実行する話はまた今度詳しくやります。

さて、今回勉強したいのは target = {'CLASS','FISH','CARS'}の部分で「テーブル」といいます。

Luaにおいて、とても特徴的なのが、データ構造が唯一その「テーブル」というもので管理されるという点です。
「テーブル」と聞いて一般的に想像する形式とは違い、Luaのテーブルは、入れ子にできたり関数を保持できたり、かなり何でもアリな概念だと思ってください。

多分、何回か掘り下げることになりうと思いますが、まずポイントとして、Luaのテーブルは
配列(ARRAY)と連想配列(HASH)の両方の性質を持つという点です。
いや正確には内部的に「Array」管理パートと「Hash」管理パートがあるといった感じですかね。
まあ、まだそんなに難しく考える必要はないです。なんでも入れれると思ってください


proc lua;
 submit;
 --配列的なテーブル定義の例 とりあえずなんか突っ込んでみる
 local sampletable = {'りんご', 999, 'ABCD', 123}

 --型を見てみる
 print("sampletableの型は",type(sampletable),"だ")
 print("===================================")


 --ループで1から4番目の要素をプリントする
 for i =1 , 4 do
   print(i,"=",sampletable[i])
 end

 --1番目の値を変えて、5番目に値を追加する
 sampletable[1]='みかん'
 sampletable[5]=456

 print("===================================")

 --配列テーブルのループ参照には、ipairs関数というものを使って以下の書き方をする。
 --とりあえず丸覚えしちゃえばいいと思う
 for i, val in ipairs(sampletable) do
  print(i, "=",val)
 end

endsubmit;
quit;

結果は

















文字と数値混ぜれたり、途中で要素増やしたり、なんかSASのarrayをゆるゆるにした感じですね。
代入の仕方は簡単ですね。{}の中に値を区切っていれるだけです。
参照の仕方も、テーブル名[要素番号]でシンプル。

ちなみに
table ={}と空でつくっておいてから
table[1]=1と値をいれるのも当然OK


Luaの通常のインデックスループは
for インデックス変数 = 開始値 , 終了値 ,加算値 do
 処理
end
といったごく普通。

ただ、テーブルの中を参照する場合、決まり文句のようなものがあって

for i, 変数 in ipairs(テーブル名) do
変数の中にテーブルの値が入っていく
end

といった書き方をします。上記の例だとvalの中にsampletableの要素が順番にはいる形で
ループが発生します。

ipairs関数はイテレータ関数と呼ばれるもので、配列としてのテーブル(keyが1から始まる数字で連続しているならハッシュでもいいけど)には
これを使います。


つぎに同じテーブルでもハッシュ的にkeyと値で管理してみます


proc lua;
 submit;
  --ハッシュ的テーブル定義の例 とりあえずなんか突っ込んでみる
  local sampletable2 = {fruits='りんご', no1=999, valc='ABCD',[1]=1}

  --キーがkey1のデータと、キーが99のデータを追加してみる
  sampletable2["key1"] = "data1"
  sampletable2[99] = 100

  print("===================================")
  --キーがvalcのデータを参照
  print("キーがvalcのデータは",sampletable2.valc)
  --キーが99のデータを参照
  print("キーが99のデータは",sampletable2[99])
  print("===================================")

  --ハッシュテーブルのループ参照には、pairs関数というものを使って以下の書き方をする。
  --とりあえず丸覚えしちゃえばいいと思う
 for key, val in pairs(sampletable2) do
  print(key, "=",val)
 end

endsubmit;
quit;

結果は














local sampletable2 = {fruits='りんご', no1=999, valc='ABCD',[1]=1}
について
sampletable2テーブルのキーは
fruits
no1
valc
1
で、それぞれにデータが入ってます。

参照はsampletable2.valc でもsampletable2['valc']でもOK

ハッシュのように管理されているテーブルの場合
ipairsではなくpairs関数を使います。「key」っていう変数名はなんでもいいんですが、
慣習的にipairsではi、pairsではkeyって名前にするみたいです。

多分、もうおなかいっぱいだと思うんですけどもう少しだけ
今まで説明したのはいわゆる1行データ、1次元配列のようなパターンだけでした。
ただ、それだけだと、今後SASデータセットをLuaのテーブルにしたり
Luaのテーブルからデータセットを作る際にちょっとわかりにくくなると思います。
SASデータセットは変数とOBS、つまり2次元になるので。

多次元構造のつくり方はいたって簡単で、テーブルの要素をテーブルにしちゃえばOKですつまり

proc lua;
 submit;
 local samp3 = {};
 samp3[1] = {1,2,3};
 samp3[2] = {4,5,6};
 samp3[3] = {7,8,9};
 --local samp3 = {{1,2,3},{4,5,6},{7,8,9}};でも当然OKよ

 --中身を確認するためLuaのテーブル関数tostringを使ってみる
 print(table.tostring(samp3))

 endsubmit;
quit;






















としてみます。以下のようにきちんと2重ループにしてプリントしてもいいけど
for i, val in ipairs(samp3) do
for j, val2 in ipairs(val) do
print(i..'-'..j, "=",val2)
end
end

面倒なので、Luaのテーブル関数(またいずれ説明)tostringを使って、テーブルの中身をみます。
階層構造で表現されます。結果は以下のとおり

ハッシュタイプで階層的に表現したいのなら以下の感じかな
proc lua;
 submit;
 local samp4 = {};
 samp4.row1={col1=1,col2=2,col3=3};
 samp4.row2={col1=4,col2=5,col3=6};
 samp4.row3={col1=7,col2=8,col3=9};
 print(table.tostring(samp4))
 endsubmit;
run;























ごめんなさい、もう少しだけ。
sasのdim関数のようなものはないのかということなんですが#テーブル名かtable.size関数があります。
通常の配列的に使用されるテーブルなら結果は同じですが、キー値が文字、あるいは数字であっても1から始まって連番でなければ#の結果は狂います。狂うというか、#はテーブルのArrayパートの1以上の数値の最大値しか参照していない仕様のようです

どういうことかというと

proc lua;
 submit;
 --ハッシュ的テーブル定義の例 とりあえずなんか突っ込んでみる
  local samp5 = {1,2,3,4}
  print ('#samp5=',#samp5);
  print ('table.size=',table.size(samp5));

 --飛び番で定義してみる
  samp5[6] = 6
  print ('#samp5=',#samp5);
  print ('table.size(samp5)=',table.size(samp5));

  --0とか文字のキーとかたしてみる
  samp5[0] = 0
  samp5.key1 ="data"
  print ('#samp5=',#samp5);
  print ('table.size(samp5)=',table.size(samp5));

endsubmit;
quit;










ということです
これは気をつけないと駄目ですね


ところで最後にYoshihiro Fukiyaさんが嬉しい記事を書いてくれました!!

[みんなでLuaを勉強してみよう!!]

凄く嬉しいです。
参照の論文もおすすめです(ただ、ちょっと気になるところもあって、get関数じゃなくてget_valueじゃない?とか、書かれた時期のメンテナンスバージョンのせいなのかそのまま通すとエラーになる部分がある気がします。そんなことない?)


0 件のコメント:

コメントを投稿