
AWS Step Functionsで組み込み関数だけを使って整数の乗算を実装してみた(AWS CDK)
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部 IoT事業部の若槻です。
最近のAWS Step Functionsのアップデートで、数学演算を実施可能な組み込み関数が追加されました。
追加されたのは、ランダムな数値を取得するStates.MathRandomと、数値の加算をするStates.MathAddのみです。よって乗算や除算は未追加なります。
しかし今回、AWS Step Functionsで組み込み関数だけを使って数値(整数のみ)の乗算を実装をAWS CDKでしてみたのでご紹介します。
やってみた
利用した組み込み関数
実装で利用した組み込み関数はStates.ArrayRangeとStates.MathAddです。
States.ArrayRangeを使うと、指定した要素数の配列を作成できます。(ただし配列長の最大は1000です。)
//function
"array.$": "States.ArrayRange(1, 9, 2)"
//output
{"array": [1,3,5,7,9] }
States.MathAddを使うと2つの数値の和を取得できます。
//function
"value1.$": "States.MathAdd(6, 4)"
//output
{"value1": 10 }
実装
AWS CDK v2(TypeScript)で次のようなCDKスタックを作成します。
import { aws_stepfunctions, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class AwsCdkAppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    // States.ArrayRangeで乗数の長さの配列を取得
    const getArrayPass = new aws_stepfunctions.Pass(this, 'getArrayPass', {
      parameters: {
        'val.$': 'States.ArrayRange(1, $.multiplier, 1)',
      },
      resultPath: '$.getArrayPassOutput',
    });
    // ループ開始
    // 被乗数(multiplicand)の加算を乗数(multiplier)回だけループして積(product)を求める
    const initLoopArgs = new aws_stepfunctions.Pass(
      this,
      'initReplacementLoop',
      {
        parameters: {
          currentIndex: 1,
          product: aws_stepfunctions.JsonPath.stringAt('$.multiplicand'),
        },
        resultPath: '$.loopArgs',
      }
    );
    // States.MathAddでcurrentIndexとproductをインクリメントしていく
    const concatinateString = new aws_stepfunctions.Pass(
      this,
      'concatinateString',
      {
        parameters: {
          'currentIndex.$': 'States.MathAdd($.loopArgs.currentIndex, 1)',
          'product.$': 'States.MathAdd($.loopArgs.product, $.multiplicand)',
        },
        resultPath: '$.loopArgs',
      }
    );
    // 乗数回のループが完了したらSucceedへ進んで終了、未完了ならループを継続する
    const isLoopFinishedChoice = new aws_stepfunctions.Choice(
      this,
      'isLoopFinishedChoice'
    )
      .when(
        aws_stepfunctions.Condition.numberEqualsJsonPath(
          '$.multiplier',
          '$.loopArgs.currentIndex'
        ),
        // ループ終了
        new aws_stepfunctions.Succeed(this, 'succeed')
      )
      .otherwise(concatinateString);
    // State Machine
    new aws_stepfunctions.StateMachine(this, 'myStateMachine', {
      stateMachineName: 'myStateMachine',
      definition: getArrayPass
        .next(initLoopArgs)
        .next(concatinateString)
        .next(isLoopFinishedChoice),
    });
  }
}
上記をCDK Deployしてスタックをデプロイします。これにより次の定義のState Machineが作成されます。
{
  "StartAt": "getArrayPass",
  "States": {
    "getArrayPass": {
      "Type": "Pass",
      "ResultPath": "$.getArrayPassOutput",
      "Parameters": {
        "val.$": "States.ArrayRange(1, $.multiplier, 1)"
      },
      "Next": "initReplacementLoop"
    },
    "initReplacementLoop": {
      "Type": "Pass",
      "ResultPath": "$.loopArgs",
      "Parameters": {
        "currentIndex": 1,
        "product.$": "$.multiplicand"
      },
      "Next": "concatinateString"
    },
    "concatinateString": {
      "Type": "Pass",
      "ResultPath": "$.loopArgs",
      "Parameters": {
        "currentIndex.$": "States.MathAdd($.loopArgs.currentIndex, 1)",
        "product.$": "States.MathAdd($.loopArgs.product, $.multiplicand)"
      },
      "Next": "isLoopFinishedChoice"
    },
    "isLoopFinishedChoice": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.multiplier",
          "NumericEqualsPath": "$.loopArgs.currentIndex",
          "Next": "succeed"
        }
      ],
      "Default": "concatinateString"
    },
    "succeed": {
      "Type": "Succeed"
    }
  }
}
State Machine Graphは次のようになります。

動作確認
次のデータをInputにしてState Machineを実行します。
{
    "multiplicand": 3,
  	"multiplier": 5
}

実行が成功しました。getArrayPassStateでmultiplierの長さの配列が作成できています。
{
  "multiplicand": 3,
  "multiplier": 5,
  "getArrayPassOutput": {
    "val": [
      1,
      2,
      3,
      4,
      5
    ]
  }
}

suceedStateで3 * 5の乗数の積である15が取得できました!
{
  "multiplicand": 3,
  "multiplier": 5,
  "getArrayPassOutput": {
    "val": [
      1,
      2,
      3,
      4,
      5
    ]
  },
  "loopArgs": {
    "product": 15,
    "currentIndex": 5
  }
}

注意点
- States.ArrayRangeを使っている性質上、multiplierに指定できるのは整数のみとなります。
- 冒頭でも書きましたがStates.ArrayRangeで作成できる配列長の最大は1000です。よって乗算のmultiplierは1000以下とする必要があります。
おわりに
AWS Step Functionsで組み込み関数だけを使って整数の乗算をAWS CDKで実装してみました。
正直、乗算をするためだけに今回のような複雑なWorkflowを頑張って組むくらいなら、Lambda関数をTaskで実行した方が良いと思います。ただ、組み込み関数だけでこんなことも出来るんだぞという可能性を示すことは出来たのではないかと思います。
参考
- [アップデート] AWS Step Functionsの組み込み関数が14個追加されて配列や数値などの操作が簡単になりました | DevelopersIO
- AWS Step Functionsの組み込み関数をフル活用して置換箇所が不特定多数の場合の文字列置換をやってみた(AWS CDK) | DevelopersIO
以上










