Power Appsで素数を判定する

## 経緯
表題の通り、Power Appsで素数を判定する仕組みを考えてみました。きっかけとなった動画は以下のゲームアプリでした。

https://twitter.com/kozakitw/status/1637678989362995200

これをPower Appsでも作れないか…と考えたのですが；
1. 数字がポップアップで出現する
1. 素数でない数字をスライドさせると数字が分裂する
1. 素数をタップすると数字が消える
1. スコアが表示される
1. 連続解答数が記録される
1. 確率変動（スコア・アップ）
1. 他、諸々ありそう
 
といった辺りのことまでは現時点での自分の実力では難しいと判断しました。

　ただ、素数であるか否かを判定する機能の実装までは自分にもできそうだと思ったので、まずはこの仕組みだけを考えてみることにしました。

## 素数について整理する
一般的に、素数とは、１よりも大きい自然数であり、１と自分自身でしか割り切れない数をいいます。
１０までの数字を例にすると、素数は２、３、５、７です。

　「１よりも大きい自然数」と「１と自分自身でしか割り切れない数」であることをPower Appsに判断させるためには、以下のような条件を満たしていなければなりません。

- ｎ＝＜２である（素数でない場合、ｎは２未満である）
- ｎは、２からｎ－１の整数で割り切れない（素数でない場合、ｎは２からｎ－１のいずれかで割り切れる）
- ２からｎ－１までの数値で割り算を行い、それぞれ余剰が０になるかどうかを確認
　- ｎ＝７の場合、７÷２、７÷３ […] ７÷６を行う
- その結果に０が１つでも含まれていたら合成数、１つも０が無ければ素数である

ということで、以下、解説に移ります（２日もかかってしまいました）

## 自然数ｎを宣言し、表示する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/f0250707-239f-5b71-e9d0-0ba7924efc7e.png)
まず、上の画像のようにコントロールを配置します。
- 変数を宣言するためのボタン（btnGenerate）
- 変数を表示するためのラベル（lblNumber）
- 素数を判定するためのラベル（lblJudge）

btnGenerate の OnSelect プロパティに以下の変数を宣言します。
~~~
Set(gblNumber,RandBetween(2,999))
~~~

今回は、３桁の自然数をランダムで発生させ、その結果を変数 gblNumber としています。２未満の数（＝１）は素数ではないので２～９９９までの数をランダムで発生させます。

## 数列のテーブルを生成する

次に、１ずつ増えていく、発生させた変数までの数列を生成します。この数列は、除数となるため必要です。除数とは、１０÷２であれば、２の部分をいいます。ギャラリー（galDivisor）を配置し、Items プロパティに以下の数式を入力します。
~~~
Sequence(gblNumber-1,2,1)
~~~
　数式を入力したら、テンプレートセルにラベル（命名不要）を１つ置き、Text プロパティを ThisItem.Value としてみましょう。ラベルに表示された数字の分だけ、１４であれば２～１４の計１３行分の単一テーブル（Value）ができます。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/364a7cfd-fa16-b780-0279-8b34ba91656e.png)
　次に、作成した数列をコレクション（colPrime）とします。変数を宣言したボタンに以下の数式を入力します。
~~~
ClearCollect(
    colPrime,
    Sequence(
        gblNumber - 1,
        2,
        1
    )
)
~~~
　数式を入力したら、galDivisor を複製し、複製したギャラリー（galPrime）の Items プロパティに colPrime と入力します。入力したら、先ほど作成した galDivisor と同様のテーブルが完成します。これをデータソースとして素数を判定していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/2cdcf032-fce0-75f7-b963-63cb5018d681.png)
　なお、galDivisor も galPrime も、わざわざ作る必要はありません。視覚的に分かりやすく作業を進めるために配置しています。作業が完了したら破棄して問題ありません。

## 生成した数列に応じて除算を行う
　さて、整数ｎ（gblNumber）が素数であるかを判断するためには「素数でない場合」を考えると良いです。つまり、素数でない場合は「ｎは２からｎ－１のいずれかで割り切れる」ということです。換言すれば、割り切れた場合、整数ｎは素数ではないということです。

　例えば、整数ｎが１０の場合、２と５で割り切れてしまうため、１０は素数ではありません。整数ｎが７の場合、１と７以外で割り切れないため７は素数です。

　以上の観点から、Mod 関数を使って余剰が０か０以外の数かを判定します。この計算を生成された除数分で行うので、ForAll 関数を組み合わせます。btnGenerate の OnSelect プロパティに入っていた数式を以下に変更します。
~~~
Set(gblNumber,RandBetween(2,999));
ClearCollect(
    colPrime,
    ForAll(
        Sequence(
            gblNumber - 1,
            2,
            1
        ),
        Mod(
            gblNumber,
            Value
        )
    )
)
~~~
　数式を入力したらアプリを実行し、ボタンを押して動作を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/9d3a12f2-dfdf-a566-a64f-c826f15b303c.png)
　５７は２や４で割り切ることはできませんが、３で割り切ることができます（商は１９）
## 素数を判定する
　最後に、素数かどうかを判定していきます。余剰が０か０以外かで素数か否かが判断できるということで、CountIf 関数を使用し、colPrime の内の０だけを数えます。まずは、数を確認するために以下の数式を入力します。
~~~
CountIf(FirstN(colPrime,gblNumber-2),ThisRecord.Value=0)
~~~
数式を入力すると、５７は素数ではないため、以下の画像のように「２（※０が２つ含まれています）」と返って来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/6236e90e-3333-2397-ea5e-a1d197b49118.png)
また、colPrime に FirstN 関数を利用し、第二引数を gblNumber - 2 としているのは、５７を含めたくないからです。５７を含めると、この後の判定のノイズとなってしまうため、絞り込みをかけてやる必要がありました（下記画像参照）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/12ddf820-4e32-761a-f8ef-8f25172da5cb.png)
ここまでできたら、上の数式を If 関数で囲み、lblJudge の Text プロパティに「素数」か「合成数（素数でない数）」を表示していきます。
~~~
If(CountIf(FirstN(colPrime,gblNumber-2),ThisRecord.Value=0)=0,"素数","合成数")
~~~
　結果は以下の通りです。何度かボタンを押して確認してみましょう。チェックが難しい場合、RandBetween 関数の 999 を 10 に書き換えるなどしてみると分かりやすいはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2952933/a36d3fbe-3f49-45e6-9187-f6ecc5503090.png)
## まとめ
　今回はランダムで発生させた整数ｎに対する判定でしたが、テキスト入力コントロールに入力した値を Value 関数で数値化して判定することも可能です。応用してやってみてください。

　さて、素数ゲームですが、進捗があったらまたコチラで報告します。お楽しみに。

## 余談：３桁の素数（動作確認用）
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997