Unity学習記録 #010 コレクション（リスト）

# コレクション
複数のデータをひとまとめにして管理できる変数で代表的なものは３つ
・配列（要素数が固定されて扱われる代表的なデータ構造）
・リスト（要素の追加、削除、挿入ができるデータ構造）
・ディクショナリ（要素に "キー" と "値" がセットで格納させるデータ構造）

今回はリスト（List）について学習していきます。

## リスト（List）
リストは要素数を決める必要がなく、後から要素数の変更も可能です。要素の追加・削除・挿入といった操作ができます。
配列より汎用性は高くなりますが、PCのメモリ使用量は配列より高くなる傾向です。また要素の呼び出しに関しても、要素数が固定されている配列の方が早いようです。配列とリストはそれぞれに得意とする特徴があります。

・要素数の変更がある場合はリストが適している
・要素へのランダムアクセスが頻繁にある場合は配列が適している
・メモリ使用量を節約したい場合は配列が適している

### リストの宣言について
リストの構文は次のようになります。
```csharp
List<int> リスト名 = new List<型名>() {要素1, 要素2, 要素3, ... };
```
リストは要素数が可変なので、宣言時に要素数を指定する必要ありません。
実際に実行できるコードは次のようになります。
```csharp
//初期値を設定する書き方
List<int> numbers = new List<int>(){1, 2, 3};
List<string> names = new List<string>(){"斎藤", "鈴木", "高橋"};

//初期値を設定しない書き方
List<int> numbers = new List<int>();
List<string> names = new List<string>();
```
リストの２次元配列は、リストの中にリストをネストさせるコードの書き方になるようです。
```csharp
List<List<int>> numbers = new List<List<int>>();
       
numbers.Add(new List<int>() { 1, 2, 3 });  // 1行目の要素を追加
numbers.Add(new List<int>() { 4, 5, 6 });  // 2行目の要素を追加

Debug.Log(numbers[0][1]);
```
要素の操作について詳しく見ていきます。

### 要素の追加
ここからはリストの特徴である要素の操作について見ていきます。まずは要素の追加方法です。

・.add(値)：要素はリストの最後に追加されます
・.addRange(配列データ)：複数の要素をまとめて追加

Addという関数を使ってリスト最後に整数値の4を追加してみます。
```csharp
//リストの最後に要素を追加
List<int> numbers = new List<int>(){ 1, 2, 3 };
numbers.Add(4);

Debug.Log(numbers[3]);
```
AddRangeを使うことで値をまとめて追加することが可能です。
```csharp
//リストの最後に要素をまとめて追加
List<int> numbers = new List<int>() { 1, 2, 3 };
numbers.AddRange(new int[] { 4, 5, 6 });

Debug.Log(numbers[5]);
```
変数を使って値を追加することも可能です。
```csharp
//変数を使用して追加
List<string> names = new List<string>() { "斎藤", "鈴木", "高橋" };
string[] addNames = new string[] { "田中", "伊藤" };

names.AddRange(addNames);
Debug.Log(names[4]);
```
add関数とaddRange関数を使って要素の追加方法をまとめてみました。

### 要素の削除
要素の削除についても見ていきます。

・Remove(値)：リストで最初に出てくる値を削除
・RemoveAt(インデックス番号)：指定したインデックス番号の要素を削除
・RemoveRange(インデックス番号, 連番)：指定したインデックス番号から連続する要素を削除

```csharp
List<string> names = new List<string>() { "斎藤", "鈴木", "高橋", "田中", "伊藤", "渡辺", "山本" };

//インデックス番号を指定して削除
names.RemoveAt(2);

//最初の要素を削除
names.RemoveAt(0);

//最後の要素を削除
names.RemoveAt(names.Count - 1);

//特定の値を削除
names.Remove("田中");

for (int i = 0; i < names.Count; i++)
{
    Debug.Log(names[i]);
}
```
実行すると上から順に、"高橋" > "斎藤" > "山本" > "田中"と削除されます。
連続した要素をまとめて削除する方法は次のようになります。
```csharp
//連続する要素をまとめて削除
List<string> names = new List<string>() { "斎藤", "鈴木", "高橋", "田中", "伊藤", "渡辺", "山本"};
names.RemoveRange(2, 3);

for (int i = 0; i < names.Count; i++) Debug.Log(names[i]);
```
上記のコードは「リストのインデックス番号2の要素から3つの連続する要素を削除」という処理を実行します。

次に、特定の値を全て削除するコードも書いてみます。
.Removeは特定の値を削除するのに使われますが、リストの中に同じ値が複数ある場合、インデックス番号が最も若い要素だけを削除します。特定の値を全てリストから削除する場合は少し工夫が必要です。
```csharp
//値が0の要素を全て削除
List<int> numbers = new List<int>() { 1, 0, 0, 4, 2, 0, 3, 0 };

foreach (int element in numbers.ToList())
{
    if (element == 0) numbers.Remove(element);
}

for (int i = 0; i < numbers.Count; i++) Debug.Log(numbers[i]);
```
ここでは値が0の要素を全て削除しています。
コード内で ToList() という関数が出てきますが、ループ処理中にリストの要素数が変化するため、その都度新しいリストとして更新する必要があるためです。
また、if文やfor文で、実行するコードが1行なので、中括弧を省略して記述しています。

### 要素の挿入
指定したインデックスに値を挿入する方法も見ていきます。

・.Insert(インデックス番号, 値)

```csharp
//インデックス番号を指定して挿入
List<int> numbers = new List<int>() { 1, 2, 3, 4 };

numbers.Insert(0, -1);
numbers.Insert(3, 10);

for (int i = 0; i < numbers.Count; i++) Debug.Log(numbers[i]);
```
.Insert関数を使うことで、割り込ませたい要素の位置を自由に決めて値を挿入することができます。

## おまけ情報
### 変数の型を確認する関数 .GetType()
今回のリストの学習とは別の話になりますが、知っていると便利な関数を紹介します。
プログラム実行時のエラーを修正する作業を「デバック作業」や「デバック処理」と言ったりしますが、このデバック作業で知っておくと便利な関数が GetType()関数です。これは変数の型を出力します。
実際にコードを書いて実行するとすぐに理解できると思います。
```csharp
int a =2;
float b =5.6f;
string c = "Hello world!";
bool d = true;

Debug.Log(a.GetType()); //出力結果：System.Int32
Debug.Log(b.GetType()); //出力結果：System.Single
Debug.Log(c.GetType()); //出力結果：System.String
Debug.Log(d.GetType()); //出力結果：System.Boolean
```
実行するとint型の変数aはコンソールに「Systemu.Int32」が返ってきます。
変数aはint型であることをPCが教えてくれます。32というのはint型で設定できる数値の範囲が32ビットであることを教えてくれているようです。string型やbool型でも型に対応した結果を出力できます。

頻繁に使うほどではないと思いますが、数字とアルファベットで見分けが付かない場合など、ちょっとした確認作業に使うことがあります。

### 型推論 var

.GetType() を学習したので、併せて型を自動的に推論してくれる「var」を使った変数の宣言を見てみます。
```csharp
var a =1;
var b =5.6f;
var c = "Hello world!";
var d = true;

Debug.Log(a.GetType());
Debug.Log(b.GetType());
Debug.Log(c.GetType());
Debug.Log(d.GetType());
```
型の宣言が全てvarとなっていますが、実行してみるとそれぞれの初期値に対応した型を出力しています。
型の宣言にvarを使うとPCが初期値を識別して型を自動で決めてくれます。注意点は、varは変数の宣言時に必ず初期値を設定する必要があります。
サンプルコードでもよく見かける予約語なので紹介しました。