LoginSignup
0

More than 5 years have passed since last update.

[FileMaker]カスタム関数で再帰する回数について調べてみた

Last updated at Posted at 2017-12-03

背景

対象レコードのフィールドをList関数のように改行区切りで取りたいときに、
カスタム関数のGetFieldValuesを使用しています。

で、あるときに、レコード数がなかなか多い件数を取得したときに、
「?」が返ってきたので、調べてみました。

GetFieldValuesの説明

中身はこんな感じです。
ifをつかうよりcaseの方が実行速度が速いそうなので、
fmpro.jpに登録されている内容から少し変更しています。
(かなり前の情報だけど、今もそうなのかな?)

GetFildValue.jpg

説明

Case (
//最終行かどうかチェック
Cnt < Get ( 対象レコード数 );

  //最終行でなければ、Cnt番目の内容を取得、改行を追加して、同じカスタム関数を呼び出す(再帰する)
  //このとき、Cntに+1して、次の行をとってくる
    GetNthRecord ( Field ; Cnt ) & "¶" & GetFieldValues ( Field ; Cnt + 1 );

  //最終行の時はCnt番目の内容を返す
    GetNthRecord ( Field ; Cnt )
)

ザ・再帰処理って感じですね!
で、なんで「?」で返ってくるか調べたら、

対象レコード見てみると一万数十件。。。
再帰処理の上限回数に引っかかっていたみたいでした。
ちょっと多いけど、いっとけ!で実行してもダメですねっ!!

再帰処理の上限回数

書き方によって上限が、10,000回か50,000回になるらしいです。

ふむふむ

GetFildValuesの書き方だと、10,000回が制限で怒られたと。
じゃあ50,000回の書き方ですれば、
残りの数十件のためにスクリプト作らんでもいいなーと思ったのですが、
そこから理解するまでに時間がかかった。。。

で、結局 50,000回を上限にするには?

ここで質問されている内容を参考にさせて頂きました。
キーワードは「末尾最適化

対象レコードが2行のとき、GetFieldValuesの動きを順に見ていくと、
 1. 1レコード目をGetNthRecordで取得する。2レコード目の結果を後ろに付けるため、再帰する(関数1回目の呼び出し)
 2. GetNthRecordで2レコード目を取得して、結果を返す。(関数2回目の呼び出し)
 3. 関数1回目の呼び出しが、2回目の結果を元に、結果を返す。
となります。
つまり、関数1回目の呼び出しは、再帰する全ての処理が終わらないと終わらないんです。
で、この時は上限が10,000回まで。

末尾最適化を行うには、
 1. 関数1回目の呼び出しで結果を引数だったりグローバル変数に入れて再帰する。
 2. 2回目の呼び出しのときに、1回目の内容を含めて結果を返す。
ようにすると、関数1回目は2回目以降の処理を待たずに終われるようになり、
上限が50,000回になるそうです。

サンプル

このことを踏まえ、以下のようにしてみました。
 - 1回ずつ終了するように、結果をグローバル変数に入れていく。
 - 最後にグローバル変数の内容を返す。
 - 使ったグローバル変数のクリアを忘れずに!
ついでに元の処理を見て思った、
 - 引数に必ず1を入れなくてもよくない?
 - 関数の中で「Get ( 対象レコード数 )」が再帰する度に呼ばれているのが気になる。。。
というのも対応してみました。

/*==============================================================
名  称:GetFieldValues ( Field )
概  要:現在の対象レコードのフィールドをList関数のように改行区切りで取得する。
引数:
 Field: 改行区切りで取得したいフィールド
計算式例:GetFieldValues( Current::Field )
備考  :このバージョンは末尾最適化を行っているので、制限50000回
=============================================================*/
Case($$#Cnt = "" ;
    //初回は初期値をセット
 Let( [$$#Cnt = 1 ;
           $$#RecodeNum = Get ( 対象レコード数 ) ;
           $$#Result = ""
          ];
          GetFieldValues( Field )
    ) ;

   //最終条件
 $$#Cnt > $$#RecodeNum ;
   //最終処理
    Let( //使用したグローバル変数をクリアする
           [$$#Cnt = "" ;
            $$#RecodeNum = "";
            #Result = Left($$#Result ; Length($$#Result) - 1) ;
            $$#Result = ""
           ];
           #Result
    ) ;


    //一覧取得処理
    Let( [  $$#Result = $$#Result & GetNthRecord ( Field ; $$#Cnt ) &"¶" ;
              $$#Cnt = $$#Cnt + 1
           ];
           GetFieldValues( Field )
     )

)

#はLet関数内で使用する変数名の先頭に付けています。
$$#は、関数内で使用するグローバル変数名の先頭に付けています。

注意としては、グローバル変数を指定するときに、スクリプトなどで使用している
変数名を使用するとクリアされてしまうので、使用しない名前で定義してください。

まとめ

上記カスタム関数で、無事に一万数十件のフィールドの内容が取得できました。
最後かけ足でしたが、いかがでしょうか??

サンプルは、初回と最終条件の時はGetNthRecordしていないので、
実際は49,998レコードが限界かと思いますし、
(ただ、そこまでの件数だとなかなか返ってこないです。。。)
恐らくもっといい書き方があると思うので、
ご指摘頂けるとありがたいです!

P.S. 初Qiita投稿&初Advent Calendarでした!
文章で書くのは難しいけど楽しかった!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0