こんにちは、CX事業本部 IoT事業部の若槻です。
前回のエントリではStep Functionsでの組み込み関数による文字列置換を実装しましたが、その実装は文字列内に予め決まった個数の置換対象文字列が含まれている場合にのみ対応したものでした。よって置換箇所が複数の場合には対応できません。
例えば、数字の区切り記号は国によってカンマ,
だったりピリオド.
だったりするのですが、ピリオド区切りの数字文字列をカンマ区切りに置換したい場合、その数字の中の置換箇所の数というのは桁数によって当然変わってきます。(不特定多数となる)
そこで今回は、AWS Step Functionsの組み込み関数をフル活用することにより、置換箇所が不特定多数の場合の文字列置換を実装(AWS CDK)することができたので紹介します。
やってみた
次の5つの組み込み関数を活用して、実現することができました。
- States.StringSplit
- States.ArrayLength
- States.ArrayGetItem
- States.MathAdd
- States.Format
実装
次のようなCDK Stack定義による実装を行いました。
lib/aws-cdk-app-stack.ts
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.StringSplitで文字列分割
const getSplittedList = new aws_stepfunctions.Pass(
this,
'getSplittedListPass',
{
parameters: {
'val.$': "States.StringSplit($.inputString, '.')",
},
resultPath: '$.splittedListOutput',
}
);
// States.ArrayLengthで文字列分割の配列長を取得
const getSplittedListLength = new aws_stepfunctions.Pass(
this,
'getSplittedListLength',
{
parameters: { 'val.$': 'States.ArrayLength($.splittedListOutput.val)' },
resultPath: '$.getSplittedListLengthOutput',
}
);
// States.ArrayGetItemで配列の0番目の要素を取得(concatinatedStringに要素の文字列をループ内で連結していく)
const initLoopArgs = new aws_stepfunctions.Pass(
this,
'initReplacementLoop',
{
parameters: {
currentIndex: 1,
'concatinatedString.$':
'States.ArrayGetItem($.splittedListOutput.val, 0)',
},
resultPath: '$.loopArgs',
}
);
// ループ処理開始
// States.ArrayGetItemで現在のIndex番号の要素を取得する
const getCurrentValue = new aws_stepfunctions.Pass(
this,
'getCurrentValue',
{
parameters: {
'val.$':
'States.ArrayGetItem($.splittedListOutput.val, $.loopArgs.currentIndex)',
},
resultPath: '$.getCurrentValueOutput',
}
);
// States.MathAddでIndexをインクリし、States.Formatで置換文字列(,)t現在のIndex番号の要素を前回のconcatinatedStringの末尾に文字列結合する
const concatinateString = new aws_stepfunctions.Pass(
this,
'concatinateString',
{
parameters: {
'currentIndex.$': 'States.MathAdd($.loopArgs.currentIndex, 1)',
concatinatedString: aws_stepfunctions.JsonPath.format(
'{}{}{}',
aws_stepfunctions.JsonPath.stringAt(
'$.loopArgs.concatinatedString'
),
',',
aws_stepfunctions.JsonPath.stringAt('$.getCurrentValueOutput.val')
),
},
resultPath: '$.loopArgs',
}
);
// 全ての配列要素の処理が完了したらSucceedへ進んで終了、未完了なら次の要素を処理する
const isLoopFinishedChoice = new aws_stepfunctions.Choice(
this,
'isLoopFinishedChoice'
)
.when(
aws_stepfunctions.Condition.numberEqualsJsonPath(
'$.getSplittedListLengthOutput.val',
'$.loopArgs.currentIndex'
),
// ループ処理終了
new aws_stepfunctions.Succeed(this, 'succeed')
)
.otherwise(getCurrentValue);
// State Machine
new aws_stepfunctions.StateMachine(this, 'myStateMachine', {
stateMachineName: 'myStateMachine',
definition: getSplittedList
.next(getSplittedListLength)
.next(initLoopArgs)
.next(getCurrentValue)
.next(concatinateString)
.next(isLoopFinishedChoice),
});
}
}
States.StringSplit
により取得した置換対象文字列区切りの配列を、Choice Stateによるループ内で文字列結合して置換しています。- ループにより文字列結合をしていくアイデアは以前書いた以下の記事から着想を得ました。
- AWS Step FunctionsでDynamoDBテーブルからLastEvaluatedKeyによる繰り返し取得をしたアイテムを一つの配列に結合する(AWS CDK v2) | DevelopersIO
- 各TaskやStateの説明はコード内コメントに記載しています。
上記をCDK Deployしてスタックをデプロイすると、次の定義のState Machineが作成されます。
Definition
{
"StartAt": "getSplittedListPass",
"States": {
"getSplittedListPass": {
"Type": "Pass",
"ResultPath": "$.splittedListOutput",
"Parameters": {
"val.$": "States.StringSplit($.inputString, '.')"
},
"Next": "getSplittedListLength"
},
"getSplittedListLength": {
"Type": "Pass",
"ResultPath": "$.getSplittedListLengthOutput",
"Parameters": {
"val.$": "States.ArrayLength($.splittedListOutput.val)"
},
"Next": "initReplacementLoop"
},
"initReplacementLoop": {
"Type": "Pass",
"ResultPath": "$.loopArgs",
"Parameters": {
"currentIndex": 1,
"concatinatedString.$": "States.ArrayGetItem($.splittedListOutput.val, 0)"
},
"Next": "getCurrentValue"
},
"getCurrentValue": {
"Type": "Pass",
"ResultPath": "$.getCurrentValueOutput",
"Parameters": {
"val.$": "States.ArrayGetItem($.splittedListOutput.val, $.loopArgs.currentIndex)"
},
"Next": "concatinateString"
},
"concatinateString": {
"Type": "Pass",
"ResultPath": "$.loopArgs",
"Parameters": {
"currentIndex.$": "States.MathAdd($.loopArgs.currentIndex, 1)",
"concatinatedString.$": "States.Format('{}{}{}', $.loopArgs.concatinatedString, ',', $.getCurrentValueOutput.val)"
},
"Next": "isLoopFinishedChoice"
},
"isLoopFinishedChoice": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.getSplittedListLengthOutput.val",
"NumericEqualsPath": "$.loopArgs.currentIndex",
"Next": "succeed"
}
],
"Default": "getCurrentValue"
},
"succeed": {
"Type": "Succeed"
}
}
}
State Machine Graphは次のようになります。
動作確認
次のInputを指定してState Machineを実行します。
Input
{
"inputString": "123.456.789"
}
実行結果を見ると、succeed
Stateで123.456.789
が文字列置換された123,456,789
が取得できていますね!
注意点
States.StringSplit
の仕様として、配列への空文字列の追加はスキップされるようです。よってSplitter文字列が元の文字列の冒頭や末尾にあったり連続していたりすると、今回のState Machineの実装では置換されずに削除される動作となります。
よって.123.456
は123,456
、123.456.789.123.456.
は123,456,789,123,456
、123..456
は123,456
となります。
これで不都合がある場合は実装を改める必要がありそうです。
おわりに
AWS Step Functionsの組み込み関数をフル活用して、置換箇所が不特定多数の場合の文字列置換をやってみました。
Step Functionsはアイデア次第でこんなこともできるんだぞというのを示せたのではないかと思います。
参考
以上