Studio Happyvalley

反復処理の停止と再開を自在に制御

Javascriptの
イテレータとジェネレータで
反復処理を制御する

image: 反復処理の停止と再開を自在に制御
Toplabo
Implement an iterator

レジ打ちのように反復処理を好きな時に止めて再開する。

 | 2022/06/09

開発経緯

複数選択可の自動見積の解決方法を
  • 自動見積の複数選択の概要

    最近、仕事の依頼に多いのが自動見積の案件です。複数ステップでユーザーが好みの項目を選択していき最終的に選択した内容で自動で見積もるという機能です。
    具体例を挙げると、住宅を考えると
    1.木造か鉄筋かRCか?
    2.平屋か二階建てか三回建てか?
    3.坪数は?
    4.内容は普通か豪華か最上級か
    などを選択して最終的な見積を算出します。

    通常は一つのみ選択して進めていくんですが、今回の案件は複数選択可能案件でした。
    たとえば庭木の伐採を見積もる場合・・・。(下図を参照。)

    1.剪定か伐採か伐根か
    2.それぞれの作業の対象物は松か松以外か(松の剪定は特殊なようです)垣根か植え込みか
    3.それぞれの本数は高さは幅は
    などなど
    これらが複数選択されると問題になるのが反復処理のタイミングです。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig-1.png
    ループ処理の間でユーザーアクションを待つ

    step1、step2とすすめていくと選択した項目が増えていき
    step3で「剪定」→「松」→「高さ+本数」と選んだ内容で合計金額を算出。
    通常の反復処理はこの後次のステップへ進むのですが、複数選択が可能なので
    もう一度戻ってstep2で「松」以外に選んだ項目があるかないかを調べて、有ればstep3まで進めて合計算出。
    さらに、step2に選択項目があるだけ反復処理を繰り返す。もしも選択した項目が5つあれば5回反復することになります。
    それが終わればまたさらにstep1に戻り同じことを繰り返す。
    反復処理のネストのネストみたいなことになります。
    この一連の処理で重要なポイントがユーザーが項目を選び終えるまで反復処理を待たせる必要がある、ということです。
    ちょうどスーパーのレジ打ちの処理が近いと思います。
    ユーザーが並んでいるのがStep1。それぞれの買い物かごの中身を処理するのがStep2~。
    最後にユーザーが支払いを済ませるまで待つ--反復処理の停止。
    支払いを完了したら次の客へ移行--反復処理の再開。

    この処理が通常のループ処理ではむつかしい。
    ユーザーが選択作業をすべて終了してから一気にループ処理をできればいいんですが、
    一連の処理がネスト化されていて通常のループ処理でやろうとするとかなり複雑な条件分岐が必要になります。
    javascriptでこれらをどう処理するか悩んでいた時に見つけたのがイテレータです。
      

解説

イテレータとジェネレータ
  • MDNの定義から

    ”イテレーターはシーケンスおよび潜在的には終了時の戻り値を定義するオブジェクトです。
    MDN | 開発者向けのウェブ技術 イテレーターとジェネレーター より


    反復処理のための順番待ちの処理を複数格納する場合、通常は配列にしますが、イテレータはこの処理を順番に一回のみ消費していきます(複数回も可)。

    このイテレータにジェネレータ関数を組み合わせることで、実行が連続していない反復アルゴリズムが可能になります。
    つまり、反復処理を好きな時点で一時停止できることになります。
    イテレータとジェネレータというと別物のように感じますがジェネレーターはイテレータの一種と定義してあります。
    このジェネレータはyield と呼ばれるキーワードを使って反復処理を停止状態にすることができます。
    たとえば以下のサンプルコード。
    ※通常の関数をジェネレータにするにはfunction*とアスタリスクを付けます。

    https://studio-happyvalley.com/wp/wp-content/uploads/fig-2.png

    上のコードはクラス名”next”をクリックするとgStep.next()が実行されます。
    nextメソッドはcounterジェネレータを実行し、yieldキーワードの箇所まで実行すると停止します。
    次のgStep.next()が実行されるとまた停止した箇所から反復処理を再開し同じくyieldキーワードの箇所まで実行すると停止します。
    これでcounterを実行するたびに停止状態の反復処理が再開されます。

    イテレータとジェネレータサンプルコード:https://labo.studio-happyvalley.com/iterator/

制作 事例

ネスト構造にも対応
  • ネスト構造もシンプルに構成できます

    ネスト化も難しくはありません。よくあるネスト構造とは違い並列的な記述で可能です。
    単純に
    function* generatorStep1(index) {処理}//親
    function* generatorStep2(index) {処理}//子
    function* generatorStep3(index) {処理}//孫
    …~
    と並べ、必要な時に親子関係なくジェネレータ関数名を呼び出すだけです。
    反復処理が完了したジェネレータは動作しなくなり完了値(ここがポイントですが)を返しますので、自動的に孫から子供、親・・・先祖へと処理が伝わっていきます。
    例えると350mlのペットボトルを一つのジェネレータを考え、そのボトルの6本入りケース。
    そのケースが10個あるとします。
    それを好きな時に好きなケースから好きなだけ飲みまくると何も問題なく
    すべてのケースが空にできると思いますが
    それと同じことになります。

    サンプルではコードが長くなるので実装版を見てください。

    ネスト構造対応イテレータ実装サンプル:https://test.studio-happyvalley.com/kyokuhou/simulator/
    MDN:開発者向けのウェブ技術/javaScriptJavaScript/ガイド/イテレーターとジェネレーター:https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generators#advanced_generators

『反復処理の停止と再開を自在に制御』関連のお薦め

このページで紹介しているプログラムやビジュアルなどご依頼いただければ実装を賜ります。
お問い合わせはこちら

Permanent Exhibition