コルネの進捗や備忘録が記されたなにか

進捗や成果物や備忘録てきななにかを雑に更新していきます。

【Power Apps】コレクションにインデックスを付与する


スポンサードリンク

5/30(土)にJapan Power Platform User Group 名古屋 #5にて登壇させていただきました!
その際の登壇資料はこちらの多言語学習アプリを作成してみようになりますので、もしよかったらご覧になってください!

www.slideshare.net

さて今回は、この勉強会で発表した内容の一部にコレクションにインデックスを付与する箇所があったのですが、その方法に関して少し盛り上がっていたのでブログに纏めようと思います。

ちなみにこちらYellow11 さんが既にこちらのブログで紹介していますので併せてご確認ください!
標準コネクタのみで、Power AutomateからPower Appsにテーブルを送信する

どういうとき必要?

Split関数や外部から情報をテーブルを取得した場合、インデックス(簡単に説明するとそのレコードの添え字のことです。)が付与されていない場合が多々あります。
このような場合、例えば偶数個目のデータのみ取得したい場合や、私の資料のような入力文字と解答文字を単語ごとに比較し処理を行いたい場合などの処理が大変複雑になってしまいます。

コレクションにインデックスを付与する

今回は文字列をSplit() して得られた結果にインデックスを付与する方法を例にとって解説します。

まず、Split関数とは?という方に簡単に説明します。
Split関数を利用すると、指定した文字列を指定した文字でテーブルに分割することができる関数です。
詳しくはこちらの公式docをご覧ください。
Power Apps での関数の分割

例えば下記のような文章があったとします。
"They saluted each other by raising their wigs."
ちなみにこちらの文章の訳は"彼らは互いにカツラを上げて挨拶をした。"です。(PCの方は選択すると訳がみえます)
こちらの英文をスペースでSplitした場合このようなテーブルが作成されます。

f:id:koruneko:20200602005121p:plain

ちなみにSplit関数の式は以下になります。
英文は「TextInput1」に入力されています。

Split(TextInput1.Text, " ")

こちらの結果にインデックスを付与していこうと思います。

Power Apps でテーブルにインデックスを付与する場合新たにテーブルを作成し直す必要があります。

f:id:koruneko:20200602010032p:plain

今回例に挙げたSplit関数は実行結果としてテーブルを返してくれるため(これが図でいう元のテーブルになります。)、作成するのはインデックスを付与したテーブルだけで大丈夫です。

では実際にインデックスを付与するための式をみていきましょう。

// コレクションの初期化
Clear(splitIndexData);
// Splitでえられたレコード数分だけ処理を実行する
ForAll(
    Split(TextInput1.Text, " "),
    Collect(
        splitIndexData,
        {
            Index:CountRows(splitIndexData), // 0から連番でインデックスの付与
            Result:Result                    // Splitのレコードを順番に設定
        }
    )
)

順番に解説していきます。
まずは1行目の

Clear(splitIndexData);

です。こちらではテーブルの初期化を行っています。
どうしてここで、初期化を行っているのか?というと

数式を記述する際、レコードはどのような順序でも (可能な場合は並行して) 処理できることに注意してください。 テーブルの最初のレコードが最後のレコードの後に処理される場合があります。 順序に依存しないように注意してください。 このため、 UpdateContext 、 Clear 、 ClearCollect 関数を ForAll 関数内で使用することはできません。これらの関数は、この影響を受けやすい変数を保持するために簡単に使用できる可能性があります。 Collect を使用することはできますが、レコードが追加される順序は定義されていません。

公式docPower Apps での ForAll 関数より引用

太字の通り、ForAll内ではClearCollect関数を呼び出すことができないためです。
またこのやり方じつは確実ではなく、下線の通り処理がレコード順に実行されない場合があるようです。
しかしこちら、これまで私がForAll関数を利用してきた中でそのようなことが発生したこともありませんし、またきいたこともありません。(もしこの事象が発生した方は教えて頂けると助かります。)

続いて、ForAll関数についての簡単な解説です。
先の説明でも少しでてきたForAll関数ですが、簡単に説明すると入力テーブルのすべてのレコードに対して処理を行う関数です。
なので今回の式ではSplit関数で得られたテーブルのレコード数分だけ処理を行っています。

続いてCollectないの処理についてです。

Collect(
    splitIndexData,
    {
        Index:CountRows(splitIndexData), // 0から連番でインデックスの付与
        Result:Result                    // Splitのレコードを順番に設定
    }
)

こちらでは、"splitIndexData"という名前のテーブルに1レコードずつデータを作成しています。
"Result:Result"は左のResult が"splitIndexData"のカラムで右のResult はSplit関数のレコードのResult です。
肝心のインデックスを付与している"Index"ですが、こちらでは、「処理を実行したときの"splitIndexData"のレコード数を設定しています。

ForAll関数の処理を順番に考えていきましょう!  *今回ForAll関数の処理は順に処理されているとします。
まずSplit関数実行結果の1つ目のレコードである"They"が処理されます。
このとき"Result"にてデータの格納が行われますが、"splitIndexData"テーブルは1行目のClear関数にて初期化を行ったためデータはありません。
よってCountRows関数により得られる結果は0となり、"Index"には"0"が、"Result"には"They"が設定されます。

続いて処理が実行されるのは2レコード目の"saluted"です。
このときの"splitIndexData"には先ほどの"They"のレコードが存在するため、CountRows関数により得られる結果は1となり、"Index"には"1"が格納されますね。

ForAll内のコレクションではこのような動作が繰り返し行われることにより、インデックスの付与が行えているわけです!

おまけ

今回紹介した方法では問題がある可能性があるということを紹介しましたね。(いまのとこそのような事例には出会っていませんが...)
もしこの事象に遭遇した場合で、インデックスを付与したい!というとき、以下のような処理でインデックスの付与を行えます。

  1. "_count"という処理数をカウントする変数を1で初期化する。
  2. タイマーを設定し、以下のような設定を行う。

Timer.OnTimerEnd

Collect(
    col,
    {
        Index:_count,
        Result:Last(FirstN(Split(TextInput1.Text, " "), _count)).Result
    }
);
UpdateContext({_count:_count + 1})

Timer.Repeat

_count < CountRows(Split(TextInput1.Text, " "))

これで一応はインデックスの付与は行えます。
もちろんタイマーをスライダーに変更しても大丈夫ですよ!

今回の記事は特に非エンジニアの方にもわかりやすく説明したつもりですが、わからないとこなどあればお気軽に質問してくださいー


スポンサードリンク