5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FileMakerAdvent Calendar 2017

Day 4

[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でした!
文章で書くのは難しいけど楽しかった!

5
0
0

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
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?