プロデルでのコレクション操作とテスト

## まえがき

本記事は「[プロデル Advent Calendar 2022](https://qiita.com/advent-calendar/2022/produire)」18日目の記事です．（遅くなってすみません）

私は分野柄，普段はPythonをよく使うので，プロデルで配列に関する操作を書いているときもまずスライスや`enumerate`，`zip`のようなコレクション操作をしたあとに繰り返し文の処理を行うという流れで書きたくなります．
あまり長い複文を書こうとすると構文解析がうまくいかなくなったりするのでよくやる操作は手順にまとめてしまうが吉です．
というわけで実装することにしました．

また，このような基本的な操作でバグがあるとこれを使ったプログラムが全部ダメになってしまいます．これを防ぐためにプロデルには[単体テストを行う機能があります](https://docs.utopiat.net/rdr/manual/devenv/index.html#RdrUnit)．
見たところテストについて書かれた記事がなさそうなのでこれを使って動作確認を行いながら書いてみることにします．

コード全体は [GitHub で公開しています](https://github.com/Yukishita26/ProduireCodes/tree/main)．
「コレクション操作.rdr」を参照すれば動作するはずです．

◆動作環境情報◆
プロデル 1.9.1169
Windows 11 Pro 64ビット
メモリ:15.8 GB

あまり詳しくないことを手探りでやっているので誤りや改善点などありましたらコメントいただけると幸いです．

## 使用例

コレクションを先に作ってしまうことの利点の一つに繰り返しをブロック文で自然に書けるという点があります．

ブロック文と「みなす」文の組み合わせでカウンターと配列のインデックスアクセスで書くよりも自然な文になっているように感じます[^m]．

```
｛「A」，「B」，「C」｝の番号付き配列のすべての【要素】について『
	要素を｛番号，内容｝とみなす
	「［番号］番目：［内容］」を報告する
』ことをそれぞれ繰り返す
```

[^m]: これが自然だと感じるのはこの手の書き方を多用する Python に慣れているからかもしれません．

## テストについて

テストでは，マニュアルの通り以下のようなテストコードを書きます．

```:コレクション操作テスト.rdr
「C:\Program Files (x86)\Produire\サンプル\ソフト開発\テストツール\プロデルテストユニット.rdr」を参照する
「コレクション操作.rdr」を参照する

テストとは
	テストケースを継承する
		
	～～する手順
        ※テスト
        ーーテスト内容
	終わり
終わり
```

定義したい手順の望まれる動作をテスト内容に書きます．
例えば掛け算なら`【値1】と【値2】の積`という手順は2つの値を掛けた結果ですので，`２と３の積`は`６`になって欲しいので `２と３の積が６と等しいはず` のようなテストが考えられます．
値の等価性の他に，`例外が発生するはず` のように例外処理も考慮できます．
考えうる入力とそのときの動作のパターンについてすべて満たせるようにテストを書けば単体テストは完成です．

今回はほとんど配列を返す手順なので最初に定義する `中身がそれぞれ等しい` 手順を使って等値性を判定しています．
テストを実行すると，画像のように成功が得られました．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121120/07e9f3e2-cc3e-d4f1-52d2-a30b7e2c3124.png)

## 定義

定義した手順とテストを示します．

目次
+ [中身がそれぞれ等しい](#配列1配列と配列2配列の中身がそれぞれ等しい)
+ [連番](#初期値整数から最終値整数まで増減数整数ごとの連番)
+ [切り出す](#配列配列をはじめ番目から終わり番目までステップ整数ごとに切り出す)
+ [並列反復](#配列1配列と配列2配列の並列反復)
+ [番号付き配列](#配列配列の番号付き配列)
+ [平らにする](#多次元配列配列を平らにする)
+ [要素和](#配列1数値の配列と配列2数値の配列の要素和)
+ [要素積](#配列1数値の配列と配列2数値の配列の要素積)
+ [それぞれ掛ける](#配列数値の配列に倍率をそれぞれ掛ける)
+ [累積和](#初期値数値から配列数値の配列の累積和)


コードを全部載せると記事がめちゃくちゃ長くなるので折りたたんでいます．興味があれば適宜展開してみてください．

### 【配列1：配列】と【配列2：配列】の中身がそれぞれ等しい

条件式`□＝◇`で配列を比較すると参照が異なるものは`×`になってしまうので，テストのためにも配列の内容物が等しいかどうかを判定する手順が必要になります．
多重配列を含め対応しています．

<details>
<summary>コード</summary>

```
【配列1：配列】と，【配列2：配列】の，中身がそれぞれ等しいかどうかを判定する手順
	もし配列1の個数≠配列2の個数なら×を返す
	もし配列1の個数=0かつ配列2の個数=0なら◯を返す
	【指数：整数】=1
	配列1の個数回繰り返す
		要素1は，配列1（指数）
		要素2は，配列2（指数）
		もし（要素1の種類名が「配列」を含む）かつ（要素2の種類名が「配列」を含む）ならば
			もし要素1と要素2の中身がそれぞれ等しいでないなら×を返す
		そうでなければ
			もし要素1≠要素2なら×を返す
		もし終わり
		指数に1を足す
	繰り返し終わり
	◯を返す
終わり
```

`《繰り返す回数》回《変数名》にカウントしながら繰り返す`を使ったループだと多重配列で再帰を行ったときに変数のスコープが漏れているのかバグってしまったのでローカル変数を別途定義するループで対応しています．

</details>


<details>
<summary>テスト</summary>

```
内容一致をテストする手順
	※テスト
	「内容が一致するテスト」として｛1，2，3｝と｛1，2，3｝の中身がそれぞれ等しいが正しいはず
	「長さが異なるテスト」として｛1，2｝と｛1，2，3｝の中身がそれぞれ等しいでないが正しいはず
	「中身が異なるテスト」として｛1，2，3｝と｛1，2，4｝の中身がそれぞれ等しいでないが正しいはず
	「二重配列で一致するテスト」として｛｛1，2｝，｛3，4｝｝と｛｛1，2｝，｛3，4｝｝の中身がそれぞれ等しいが正しいはず
	「二重配列で中身が異なるテスト」として｛｛1，2｝，｛3，4｝｝と｛｛1，2｝，｛3，4，5｝｝の中身がそれぞれ等しいでないが正しいはず
	「二重配列で一致するテスト」として｛｛｛1，2｝，｛3，4｝｝｝と｛｛｛1，2｝，｛3，4｝｝｝の中身がそれぞれ等しいが正しいはず
	「多重配列で中身が異なるテスト」として｛｛｛1，2｝，｛3，4｝｝｝と｛｛1，2｝，｛3，4｝｝の中身がそれぞれ等しいでないが正しいはず
終わり
```

</details>

:::note info
用例
｛1，2，3｝と｛1，2，3｝の中身がそれぞれ等しい ： ◯
｛｛1，2｝，｛3，4｝｝と｛｛1，2｝，｛3，4｝｝の中身がそれぞれ等しい ： ◯
:::

### 〈【初期値：整数】から〉【最終値：整数】まで〈【増減数：整数】ごと〉の連番

`〈〉`で囲った目的語は省略可能[^s]なことを表します．
`《変数名》を《初期値》から《増減数》ずつ増やしながら《最終値》まで繰り返す` に準拠して繰り返す変数の配列を作ります．
Python の `range(初期値, 最終値+1, 増減数)` に相当します．最終値を含むことに注意

[^s]: 実際はそれがないパターンの手順定義を用意している

<details>
<summary>コード</summary>

```
【初期値：整数】から，【最終値：整数】まで，【増減数：整数】ごとの，連番を求める手順
	データ=｛｝
	値を初期値から増減数ずつ増やしながら最終値まで繰り返す
		データへ値を追加する
	繰り返し終わり
	データを返す
終わり

【初期値：整数】から，【最終値：整数】までの，連番を求める手順
	初期値から最終値まで1ごとの連番を返す
終わり

【個数：整数】までの，連番を求める手順
	1から個数までの連番を返す
終わり
```

省略可能引数を設定したい場合は，このように補語の異なるオーバーロードを書くと実現できます．

</details>


<details>
<summary>テスト</summary>

```
連番をテストする手順
	※テスト
	「最終値までの連番のテスト」として5までの連番と｛1，2，3，4，5｝の中身がそれぞれ等しいが正しいはず
	「初期値から最終値までの連番のテスト」として2から5までの連番と｛2，3，4，5｝の中身がそれぞれ等しいが正しいはず
	「スキップする連番のテスト」として1から5まで2ごとの連番と｛1，3，5｝の中身がそれぞれ等しいが正しいはず
	「負にスキップする連番のテスト」として5から1まで-2ごとの連番と｛5，3，1｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
5までの連番 ： ｛1，2，3，4，5｝
2から5までの連番 ： ｛2，3，4，5｝
1から5まで2ごとの連番 ： ｛1，3，5｝
5から1まで-2ごとの連番 ： ｛5，3，1｝
:::

### 【配列：配列】を〈【はじめ】番目から〉〈【終わり】番目まで〉〈【ステップ：整数】ごとに〉切り出す

組み込みの `【自分：配列】の【開始】番目から〈【個数】個〉切り出す` の拡張です．（最初の助詞が`の`→`を`）
切り出す個数ではなく番号で指定します．
Python の `配列[はじめ:終わり+1:ステップ]` に相当します[^p]．

[^p]: ただし，参照ではなく新たな配列を生成しているのでパフォーマンスは低下します．

<details>
<summary>コード</summary>

```
【配列：配列】を，【はじめ：整数】番目から，【終わり：整数】番目まで，【ステップ：整数】ごとに，切り出す手順
	もしステップ<0ならば
		長さ=配列の個数
		コピー=配列のクローン
		コピーを逆に並べ替える
		コピーを（長さ-終わり+1）番目から（長さ-はじめ+1）番目まで（-ステップ）ごとに切り出したものを返す
	もし終わり
	データ=｛｝
	【番号】をはじめからステップずつ増やしながら終わりまで繰り返す
		データへ配列（番号）を追加する
	繰り返し終わり
	データを返す
終わり

【配列：配列】を，【はじめ：整数】番目から，【終わり：整数】番目まで，切り出す手順
	配列をはじめ番目から終わり番目まで1ごとに切り出したものを返す
終わり

【配列：配列】を，【終わり：整数】番目まで，【ステップ：整数】ごとに，切り出す手順
	配列を1番目から終わり番目までステップごとに切り出したものを返す
終わり

【配列：配列】を，【終わり：整数】番目まで，切り出す手順
	配列を1番目から終わり番目まで1ごとに切り出したものを返す
終わり

【配列：配列】を，【はじめ：整数】番目から，【ステップ：整数】ごとに，最後まで切り出す手順
	【終わり】=配列の個数
	配列をはじめ番目から終わり番目までステップごとに切り出したものを返す
終わり

【配列：配列】を，【はじめ：整数】番目から，最後まで切り出す手順
	【終わり】=配列の個数
	配列をはじめ番目から終わり番目まで1ごとに切り出したものを返す
終わり
```

</details>


<details>
<summary>テスト</summary>

```
スライスをテストする手順
	※テスト
	リストは，10までの連番
	「「(1..10)[2:8:2]」」としてリストを2番目から8番目まで2ごとに切り出したものと，｛2，4，6，8｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[2:8:-2]」」としてリストを2番目から8番目まで(-2)ごとに切り出したものと，｛8，6，4，2｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[3:6]」」としてリストを3番目から6番目まで切り出したものと，｛3，4，5，6｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[2::3]」」としてリストを2番目から最後まで3ごとに切り出したものと，｛2，5，8｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[2::-3]」」としてリストを2番目から最後まで-3ごとに切り出したものと，｛10，7，4｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[:8:3]」」としてリストを8番目まで3ごとに切り出したものと，｛1，4，7｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[:5]」」としてリストを5番目まで切り出したものと，｛1，2，3，4，5｝の中身がそれぞれ等しいが正しいはず
	「「(1..10)[6:]」」としてリストを6番目から最後まで切り出したものと，｛6，7，8，9，10｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
10までの連番を2番目から8番目まで2ごとに切り出す ： ｛2，4，6，8｝
10までの連番を6番目から最後まで切り出す ： ｛6，7，8，9，10｝
10までの連番を2番目から最後まで-3ごとに切り出す ： ｛10，7，4｝
:::

### 【配列1：配列】と【配列2：配列】の並列反復

### 【配列1：配列】と【配列2：配列】を【充填値】で埋めながら並列反復

2つの配列をそれぞれ2つ組にまとめた配列を作ります．
Python の `zip(配列1，配列2)` に相当します．
単に `～並列反復` とすると短い方の配列に合わせてうち止めになります．
`～埋めながら並列反復` では足りない分を `充填値` で埋めていきます．

<details>
<summary>コード</summary>

```
【配列1：配列】と，【配列2：配列】の，並列反復を求める手順
	長さ=｛配列1の個数，配列2の個数｝の最小値
	データ=｛｝
	長さ回，指数にカウントしながら繰り返す
		データ（指数）=｛配列1（指数），配列2（指数）｝
	繰り返し終わり
	データを返す
終わり

【配列1：配列】と，【配列2：配列】を，【充填値】で，埋めながら並列反復を求める手順
	長さ=｛配列1の個数，配列2の個数｝の最大値
	データ=｛｝
	長さ回，指数にカウントしながら繰り返す
		ａ=配列1（指数）
		もしａが無ならａ=充填値
		ｂ=配列2（指数）
		もしｂが無ならｂ=充填値
		データ（指数）=｛ａ，ｂ｝
	繰り返し終わり
	データを返す
終わり
```

</details>


<details>
<summary>テスト</summary>

```
並列反復をテストする手順
	※テスト
	リスト1は，｛1，2，3｝
	リスト2は，｛4，5，6｝
	リスト3は，｛4，5，6，7，8｝
	「並列反復のテスト」としてリスト1とリスト2の並列反復と｛｛1，4｝，｛2，5｝，｛3，6｝｝の中身がそれぞれ等しいが正しいはず
	「長さが異なる並列反復のテスト」としてリスト1とリスト3の並列反復と｛｛1，4｝，｛2，5｝，｛3，6｝｝の中身がそれぞれ等しいが正しいはず
	「長さが異なる埋めながら並列反復のテスト」としてリスト1とリスト3を0で埋めながら並列反復と｛｛1，4｝，｛2，5｝，｛3，6｝，｛0，7｝，｛0，8｝｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
｛1，2，3｝と｛4，5，6｝の並列反復 ： ｛｛1，4｝，｛2，5｝，｛3，6｝｝
｛1，2，3｝と｛4，5，6，7，8｝の並列反復 ： ｛｛1，4｝，｛2，5｝，｛3，6｝｝
｛1，2，3｝と｛4，5，6，7，8｝を0で埋めながら並列反復 ： ｛｛1，4｝，｛2，5｝，｛3，6｝，｛0，7｝，｛0，8｝｝
:::

### 【配列：配列】の番号付き配列

配列の各要素にインデックスをつけた配列を作ります．
Python の `enumerate(配列)` に相当します．

<details>
<summary>コード</summary>

```
【配列：配列】の，番号付き配列を求める手順
	データ=｛｝
	配列の個数回，指数にカウントしながら繰り返す
		データ（指数）=｛指数，配列（指数）｝
	繰り返し終わり
	データを返す
終わり
```

</details>


<details>
<summary>テスト</summary>

```
番号付き配列をテストする手順
	※テスト
	リストは，｛「A」，「B」，「C」｝
	リストの番号付き配列と｛｛1，「A」｝，｛2，「B」｝，｛3，「C」｝｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
｛「A」，「B」，「C」｝の番号付き配列 ： ｛｛1，「A」｝，｛2，「B」｝，｛3，「C」｝｝
:::

### 【多次元配列：配列】を平らにする

多次元配列をフラットな配列にします．
深さに関わらず1次元配列に展開されます．

<details>
<summary>コード</summary>

```
【多次元配列：配列】を，平らにする手順
	【データ：配列】=｛｝
	【指数：整数】=1
	多次元配列の個数回繰り返す
		【内容物】は，多次元配列（指数）
		もし内容物の種類名が「配列」を含むならば
			内容物を平らにしたものをデータへ一括追加する
		そうでなければ
			内容物を，データへ，追加する
		もし終わり
		指数に1を足す
	繰り返し終わり
	データを返す
終わり
```

</details>


<details>
<summary>テスト</summary>

```
平らにするテストをする手順
	※テスト
	リストは，｛｛｛1，2｝，｛｛3，｛4｝，｛｝｝｝｝｝
	リストを平らにしたものと，｛1，2，3，4｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
｛｛｛1，2｝，｛｛3，｛4｝，｛｝｝｝｝｝を平らにしたもの ： ｛1，2，3，4｝
:::

### 【配列1：数値の配列】と【配列2：数値の配列】の要素和

### 【配列1：数値の配列】と【配列2：数値の配列】の要素積

### 【配列：数値の配列】に【倍率】をそれぞれ掛ける

配列の各要素を足し算/掛け算した結果の配列を作ります．
要素和/積では配列の長さが異なる場合短い方に合わせます．

<details>
<summary>コード</summary>

```
【配列1：数値の配列】と，【配列2：数値の配列】の，要素和を求める手順
	長さ=｛配列1の個数，配列2の個数｝の最小値
	データ=｛｝
	長さ回，指数にカウントしながら繰り返す
		データ（指数）=配列1（指数）+配列2（指数）
	繰り返し終わり
	データを返す
終わり

【配列1：数値の配列】と，【配列2：数値の配列】の，要素積を求める手順
	長さ=｛配列1の個数，配列2の個数｝の最小値
	データ=｛｝
	長さ回，指数にカウントしながら繰り返す
		データ（指数）=配列1（指数）×配列2（指数）
	繰り返し終わり
	データを返す
終わり

【配列：数値の配列】に，【倍率：数値】を，それぞれ掛ける手順
	データ=｛｝
	配列のすべての要素について『
		倍率と要素の積をデータへ追加する
	』ことをそれぞれ繰り返す
	データを返す
終わり
```

</details>

<details>
<summary>テスト</summary>

```
要素和をテストする手順
	※テスト
	リスト1は，5までの連番
	リスト2は，3から15まで2ごとの連番 ーー｛3，5，7，9，11，13，15｝
	リスト1とリスト2の要素和と｛4，7，10，13，16｝の中身がそれぞれ等しいが正しいはず
終わり

要素積をテストする手順
	※テスト
	リスト1は，5までの連番
	リスト2は，3から15まで2ごとの連番 ーー｛3，5，7，9，11，13，15｝
	リスト1とリスト2の要素積と｛3，10，21，36，55｝の中身がそれぞれ等しいが正しいはず
終わり

スカラー倍をテストする手順
	※テスト
	リストは，5までの連番
	リストに3をそれぞれ掛けたものと，｛3，6，9，12，15｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
｛1，2，3，4，5｝と｛3，5，7，9，11｝の要素和 ：｛4，7，10，13，16｝
｛1，2，3，4，5｝と｛3，5，7，9，11｝の要素積 ：｛3，10，21，36，55｝
｛1，2，3，4，5｝と｛3，5，7，9，11，13，15｝の要素積 ：｛3，10，21，36，55｝
｛1，2，3，4，5｝に3をそれぞれかけたもの ：｛3，6，9，12，15｝
:::


### 〈【初期値：数値】から〉【配列：数値の配列】の累積和

先頭から順に値を足していったときの結果を作ります．
デフォルトでは初期値は0．

<details>
<summary>コード</summary>

```
【初期値：数値】から，【配列：数値の配列】の，累積和を求める手順
	データ=｛初期値｝
	配列のすべての要素について『
		データの末尾と要素の和をデータへ追加する
	』ことをそれぞれ繰り返す
	データを返す
終わり

【配列：数値の配列】の，累積和を求める手順
	0から配列の累積和を返す
終わり
```

</details>


<details>
<summary>テスト</summary>

```
累積和をテストする手順
	※テスト
	リストは，5までの連番
	リストの累積和と｛0，1，3，6，10，15｝の中身がそれぞれ等しいが正しいはず
	1からリストの累積和と｛1，2，4，7，11，16｝の中身がそれぞれ等しいが正しいはず
終わり
```

</details>

:::note info
用例
5までの連番の累積和 ： ｛0，1，3，6，10，15｝
1から（5までの連番）の累積和 ： ｛1，2，4，7，11，16｝
:::