Power Appsで行列の積を計算しよう
2021-08-03
azblob://2022/11/11/eyecatch/2021-08-02-multiplication-matrix-power-apps-000.jpg

 こんにちは。先日、終業後に某有名ラーメン店に行ったところ、やはりこの暑さの中でも関係なく大行列ができていたので席に着くのも一苦労でした。みなさんも行列に並ぶ際はくれぐれも熱中症には気を付けましょう。

 ところで、行列といえば線形代数ですよね。その初歩の初歩である行列の積(掛け算)の計算を多くの人はやったことがあるのではないでしょうか。(忘れてしまった方、そもそもご存じでない方はググってみてください!)

例えば2行2列正方行列同士ならものの数十秒で手計算できますよね。では3行3列正方行列同士はどうでしょうか。これも当然2行2列の時よりは時間がかかるものの簡単に手計算できるでしょう。それでは4行4列、5行5列、...と数が大きくなったときはどうでしょうか。確かに手計算できるとは思いますが、計算ミスのリスクも大きくなるのでやりたくないですよね。そんな時に便利なのが計算機です。ググってみれば行列計算ができるページが何個か出てくると思うのでそれを使うのでもいいのですが、せっかくなので今回は作ってみようと思います。

 実は先日投稿したPower Appsで表形式の入力フォームを作る~ギャラリー in ギャラリー~ #Power Platformリレー | cloud.config Tech Blog (cloud-config.jp)という記事で扱った、ギャラリー in ギャラリーを応用すればすごく簡単にできてしまうのです。これを読んでいる皆さんにもこの簡単さを実感していただければと思います。

行列を表すデータソースを作る

 まずはデータソースを作っていきましょう。行をi、列をjとすれば、行列は各i行j列成分に値を持っているのでテーブルはこのようにします。

列名
iNumber
jNumber
valueNumber

より具体的には下の図に示しました。

掛け算に使う行列の値を入力する表を作る

 さて、実際にアプリを作っていきます。空のキャンバスアプリを作り、新しく空白のページを用意したらOnVisibleプロパティに以下のコードを貼り付けます。

このコードのこころは、一つ目のClearCollectで掛け算の左側の行列を表すコレクションmatrixLeftを定義して、RemoveIfでコレクションを空にしています。二つ目のClearCollectのmatrixRightも同様に掛け算の右側の行列を定義します。三つ目のClearCollectではindexというコレクションを定義していますが、これは行列の成分を生成する際に使います。そして二重のForAll文では、すべての(i,j)の組み合わせに対してvalue:0であるレコードを先ほど作ったmatrixLeftとmatrixRightに追加しています。コレクションindexは[1, 2, 3]だったので、作られる(i,j)の組み合わせは(1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3)となりますが、indexの中身を[1, 2, 3, 4]のようにすれば(1,1), (1,2), (1,3), ... ,(4,2), (4,3), (4,4)のようになります。


ClearCollect(
    matrixLeft,
    {
        i: 0,
        j: 0,
        value: 0
    }
);
RemoveIf(
    matrixLeft,
    true
);
ClearCollect(
    matrixRight,
    {
        i: 0,
        j: 0,
        value: 0
    }
);
RemoveIf(
    matrixRight,
    true
);

ClearCollect(
    index,
    1,
    2,
    3
);

ForAll(
    index As i,
    ForAll(
        index As j,
        Patch(
            matrixLeft,
            Defaults(matrixLeft),
            {
                i: i.Value,
                j: j.Value,
                value: 0
            }
        );
        Patch(
            matrixRight,
            Defaults(matrixRight),
            {
                i: i.Value,
                j: j.Value,
                value: 0
            }
        );
    );
    
);

これで、画面を開くと同時に掛け算に使う二つの3行3列正方行列が生成されます。

 それでは次に行列の各成分の値を変更するフォームを作っていきます。つまりギャラリー in ギャラリーの出番です。

まずはツールバーの挿入から縦方向(空)のギャラリーを追加し、その先頭の行を選択した状態で同様に横方向(空)のギャラリーを追加します。

どちらのデータソースにも先ほど作成したindexを設定します。

そして横方向(空)のギャラリーをクリックして、そこにテキスト入力コントロールを挿入します。

こんな感じの3×3マスの入力フォームができるはずです。

行番号がわかりやすいように縦方向(空)のギャラリーの先頭の行を選択し、そこにラベルを追加します。ラベルの中身はデータソース(=index)の値が順番に入っています。

今度はテキスト入力コントロールをクリックし、そのDefaultプロパティに次のコードを入力します。

これは何をしているかというと、現在の行と現在の列に一致するレコードをmatrixLeftの中から探してきて(Filter)、テキスト入力にそのレコードのvalue列の値を表示しています。

First(
    Filter(
        matrixLeft,
        ThisRecord.i = Value([参照するラベル名].Text),
        ThisRecord.j = ThisItem.Value
    )
).value

同じテキスト入力コントロールのOnChangeプロパティに次のコードを入力します。これはPatch関数を使うことで、テキスト入力に数字が入力されたときに、matrixLeftのi行j列の値を入力した数字で上書きする操作を表しています。

Patch(
    matrixLeft,
    LookUp(
        matrixLeft,
        ThisRecord.i = Value([参照するラベル名].Text) && ThisRecord.j = ThisItem.Value
    ),
    {
        i:  Value([参照するラべル名].Text),
        j: ThisItem.Value,
        value: Value([参照するテキスト入力コントロール名].Text)
    }
)

値を入力して確かめてみると、コレクションの中のvalue列の値が確かに入力した値に更新されていますね。

 同じようにしてmatrixRight用の表も作成します。コピペしてmatrixLeftの部分をmatrixRightにしてもかまいません。

計算結果を出力する表を作る

 次に計算結果を出力する表を作ります。

ギャラリーの中にギャラリーを入れ、データソースをindexにするのは同じなのでコピペでいいのですが、計算結果については編集する必要はないため、先ほど横方向(空)のギャラリーに埋め込んだテキスト入力コントロールの代わりにラベルを埋め込みます。

追加したラベルのTextプロパティに以下のコードを追加します。

ここでは何をやっているかというと、「Filter(matrixLeft,~)」で現在の行に一致するレコードをmatrixLeftから持ってきています。そしてForAllによってここで持ってきた各レコードに対して、そのレコードの列の番号と一致する行でかつ現在の列に一致するレコードをmatrixRightから持ってきて(Filter(matrixRight,~))から、それらのvalue値同士を掛け算しています。最後にSumでそれらの値を足し上げています。要は行列の掛け算の約束をコードに落としただけです。

Sum(
    ForAll(
        Filter(
            matrixLeft,
            ThisRecord.i = Value([参照するラベル名].Text)
        ) As lef,
        First(
            Filter(
                matrixRight,
                ThisRecord.i =  lef.j ,
                ThisRecord.j = ThisItem.Value
            )
        ).value * lef.value
    ),
    Value
)

これで完成です。お疲れさまでした。実際に値を入れて計算してみると...

しっかり正しい値が出力されています!(ついでに列のラベルも追加しました)

行列のサイズを可変にする

 これで3行3列正方行列の計算ができるようになりました。しかしこれだけではつまらないので、行列のサイズを可変にしましょう。

 そのためにまずドロップダウンコントロールを追加して、ItemsプロパティにSequence(10)と入力します。これで1~10の選択肢を持つドロップダウンができました。

 編集すべき箇所は残すところあと二つです。

 一つ目はOnVisibleプロパティです。ClearCollect(index,1,2,3)の部分を以下のコードに変えます。

ClearCollect(
    index,
    Sequence([追加したドロップダウンコントロール名].Selected.Value,1)
);

これによって、画面を開いたときに作られるコレクションindexの中身が、ドロップダウンコントロールで選択される値によって変わります。例えば5が選択されていたとすれば、コレクションindexは[1,2,3,4,5]となります。今回作ったドロップダウンの値は1~10まで選択できるので、最大で10行10列正方行列同士の掛け算まで対応できます。

 二つ目にいじるのはドロップダウンコントロールのOnChangeプロパティです。ここにはOnVisibleに入っているコードをそのままコピペします。こうすることで、ドロップダウンの値が変更されたときに、選択されたサイズの行列を生成することができます。

さて、これで好きなサイズの正方行列の積を計算できるようになりました。

さすがに10行10列ともなると最初の表が反映されるのに時間がかかりますが、反映されてしまえば問題なく計算に使えます。10行10列は入力するのがしんどかったので、5行5列の掛け算を示して終わりにしようと思います。

最後に

 今回は簡単のため正方行列の掛け算に限定しましたが、カスタマイズ次第でn行k列とk行m列の積、行列とベクトルの積、固有値計算などいろんな応用ができそうですね。