今回はforeachとfor文についての記事を書いていきたいと思います。 Show
for文とforeachにはそれぞれ特徴があり、使い分けが必要ですよね。 確か私はfor文は高速だけど、foreachは可読性が高いといった教え方をされたような気がします。 ただふと疑問に思ったのです、「本当にfor文の方が高速なのか?」と。 というわけで実験をしていきます。
Unity2019.2.11f1
最初にややネタバレちっくですが、for文とforeachの機能はまったく違います。 ・for文:ある条件が成立するまで繰り返す ・foreach文:ある列挙インターフェイスが列挙する要素を繰り返して1つ1つ取得する foreachはIEnumerableインターフェイス(厳密にはGetEnumeratorメソッド)を実装していなければなりません。 そしてGetEnumeratorメソッドの返り値はEnumerator構造体(IEnumerator<T>とIEnumeratorを実装)なので、MoveNextメソッドとCurrentプロパティを実装、つまりは次のデータと現在のデータしか保持していないのです。(IDisposable,Resetメソッドもありますが省略します)
まずは普通にシンプルな勝負をしましょう。 using System.Collections.Generic; using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { List<int> target = Enumerable.Range(0, 10000000).ToList(); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); for(int i = 0; i < target.Count; i++) { sum += target[i]; } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in target) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms"); } }結果は…
さすがfor文、foreachよりも早い速度を出してきました。
ただしランダムアクセス性がない(シーケンシャルアクセス)コレクションのときは結果が変わります。 ランダムアクセス性がないとは、numbers[1]みたく普通には要素番号を指定して要素を取得したりできないことです。例えばIEnumerable<T>ですね。 これを要素番号を指定するとなると、LinqのElementAt(IEnumerableの拡張メソッド)を使うしかないでしょう。 using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { var numbers = Enumerable.Range(0, 10000000); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); for(int i = 0; i < numbers.Count(); i++) { sum += numbers.ElementAt(i); } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms {sum}"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in numbers) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms {sum}"); } }
foreachは先程とほぼ変わりませんが、for文は一気に遅くなってしまいましたね。 一応以下のようにコードを変更すれば少し早くなりますが、やはりforeachには勝てません。 int count = numbers.Count(); for(int i = 0; i < count; i++) { sum += numbers.ElementAt(i); }
ただし、さらに工夫を重ねればforeachと並ぶことができます。 using UnityEngine; using System.Linq; public class Test : MonoBehaviour { private void Start() { var numbers = Enumerable.Range(0, 10000000); int sum = 0; System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); var enumerator = numbers.GetEnumerator(); for(;enumerator.MoveNext();) { sum += enumerator.Current; } sw.Stop(); Debug.Log($"for: { sw.ElapsedMilliseconds }ms {sum}"); sw.Reset(); sum = 0; sw.Start(); foreach(var item in numbers) { sum += item; } sw.Stop(); Debug.Log($"foreach: { sw.ElapsedMilliseconds }ms {sum}"); } }
ほとんど同じ値にまで近づけることができました。 ただこのfor文の中身は、もはやforeachの内部的な仕組みとほぼ同じになっています。 わざわざこんなコードを書くなら、foreachを書いたほうがよいでしょう。
少しはforeachの名誉を守りましたが、速度的にはfor文の方が早い場合が多いと思います。 ただ、やはり可読性などの面からもfor,foreachをうまく使い分けていきたいですね!
裏口からのC実践入門 ~バッドノウハウを踏み越えて本物へ!! 「Unityで学ぶC#入門」の連載第12回は「クラスの基本的な使い方を学ぶ」でした。 第13回の今回はList(リスト)の基本的な使い方を学びます。 データをまとまりとして保存する場合に利用するのが配列ですが、用途によっては配列が適切ではない場合があります。 例えば、RPGのゲームでアイテムの一覧を変数に保存する場合、アイテムを増やしていく時に配列だとアイテム数に上限が出来てしまうので適切ではありません。このような場合に使うのがlistになります。 今回はlistの基本的な使い方と配列との違いに焦点を当てて解説します。 List(リスト)とはlistとは、同じデータ型の値をまとめて保存し利用するためのものです。 配列と似ていますが、配列と異なる点は要素数が固定ではないという点です。また、Listの方が少し処理に時間がかかるという点もあります。 そのため、C#において複数のデータを同時に扱いたい場合に、データの要素を増減させる時はlistを使い、データ要素数が変わらない時は配列を使うことが多いです。 配列については、第10回の講義で詳しく解説しているので、配列についても学びたい方は是非ご一読ください。 Listの基本的な使い方ではまずListの基本的な使い方を解説していきます。 Listの宣言方法から、要素の追加・更新・削除・取得など基本的なListのメソッドの説明になります。 以下は目次になります(目次の項目をクリックすると解説部分まで遷移します) Listを使うために必要なGenericListを使う場合は、冒頭に以下のスクリプトが存在しなければならないので、必ず記述するようにしてください。(このGenericの1機能がListになるためです。)
using System.Collections.Generic;
Listの宣言:newListも変数の一種になるので、宣言する必要があります。 Listの宣言方法は以下になります。
List<int> List1 = new List<int>();
このスクリプトではint型のListを作成しましたが、String型やGameObject型など、Unityで使える型での宣言も可能です。 Listの初期化Listを初期化する場合は以下のスクリプトを使用します。
//List1を初期化 var List1 = new List<int>();
考え方はシンプルで、既に作成したListに新しい空白のListをnewで宣言してあげればよいのです。 要素の追加:Add,InsertListは要素を追加できる配列のようなものなので、要素を追加したい場合は以下のスクリプトを利用します。
//要素"2"をListの末尾に追加する List1.Add(1); //リストの4番目に要素"4"を挿入する List1.Insert(3, 4);
上記は末尾に追加する方法と指定したn番目に追加する方法になります。 注意事項として、先頭の要素を0番目と扱うので先頭に挿入する場合は0を、n-1番目を選択したい場合はnをInsertで指定する必要があります。 例えばint型Listがn個の要素を持っている時のn番目は以下のような対応になっています。 複数要素を同時追加:AddRange,InsertRangeListに複数要素を同時に追加することもできます。 この場合は他のListもしくは配列(Array)を追加することになるので、事前に準備が必要です。 スクリプトは以下になります。
//List1に複数要素(otherList)を追加 List1.AddRange(otherList); //List1に複数要素を2番目に追加 List1.InsertRange(3,other.List);
要素の更新要素を更新する方法は以下になります。
//リストの2番目の要素を"1"に更新する list[1] = 1;
追加と同様、要素の指定は(n+1)でn番目を指定できるのでご注意ください。 要素の取得Listの要素を取得したい場合は以下のスクリプトを使います。
//listの0番目の要素を抽出する int int1 = list[0];
追加と同様、要素の指定は(n+1)でn番目を指定できるのでご注意ください。 要素の削除:Remove,RemoveAt,RemoveRange,ClearListの要素を削除したい場合、以下のスクリプトを使います。 あるn番目を削除、m~n番目の要素全てを削除、全て削除、指定した値が含まれている要素を削除などが行えます。
//3番目の要素を削除 list.RemoveAt(2); //要素"100"を削除する list.Remove(100); //2番目~5番目の要素を削除する list.RemoveRange(1,4); //全ての要素を削除する list.Clear();
Listの要素並び替え:Sort要素を昇順や降順に並び替えることもできます。 この場合に使用するメソッドはSortです。
List List1 = new List (4,3,2,1,5); //Sortで昇順に並び替え(1,2,3,4,5) List1.Sort(); //ラムダ式を使って降順に並び替え(5,4,3,2,1,) List1.Sort(( a , b ) => b - a);
ちなみに、Sortの処理速度ですが基本的にはList.Sortの方がラムダ式のソートより速くなります。 要素のインテックスの取得:IndexOf指定した値を持つ要素が何番目の要素なのかを取得することもできます。 その際に使用するスクリプトは以下です。
//List1の中の「100」という値を持つ要素のインテックスを取得 var index = List1.IndexOf(100);
指定した要素が見つかった場合、「0」から始まるインデックス番号を返します。 一方で見つからなかった場合には「-1」を返します。 要素数の取得:CountListに何個の要素が存在するのかを取得することもできます。 その際に使用するスクリプトは以下です。
//listの要素数をcount1に取得する int count1 = list.Count;
要素の存在判定:CountainsListの要素の中に「100」が存在するかどうかを判定することができます。
//listの中に「100」という要素が存在するか bool bool1 = list.Contains(100);
この関数で返ってくる値はBool値(true/false)なので、Bool型の変数を用意する必要があります。 要素の検索:Find,FindLast,FindAll条件を設定して、その条件に当てはまる要素を取得することもできます。 この際に使用するメソッドはFind,FindLast,FindAllになります。 FindはListの先頭から順番に検索し、最初に当てはまるものを返すメソッドになります。 FindLastは後ろから検索し、最初に当てはまるものを返すメソッドになります。 FindAllは条件に当てはまる要素全てを返すメソッドになります。 例えば、偶数の要素を抽出したい場合に使うスクリプトは以下になります。
List List1 = new List (6,7,2,8,10); //Findで6が返ります。 int result1 = List1.Find (n => n%2 == 0); //FindLastで10が返ります。 int result2 = List1.FindLast ( n => n%2 ==0); //FindAllでは6,2,8,10が返ります。 int result3 = List1.FindAll ( n => n%2 == 0);
要素を逆順に並び替える:ReverseListの要素を逆順にする関数も存在します。 例えば、「5,6,1,3,9」というlistが存在した場合、RevListには「9,3,1,6,5」が代入されます。
//listの要素を逆順に並び替える List Revlist = list.Reverse();
Listを配列に変換する:ToArrayListを配列に変換することもできます。
//listを配列に変換する Int[] Ar = list.ToArray();
Listの実践的な使い方Listには様々な使用例がありますが、その中でもよく使用する、複数メソッドを組み合わせたスクリプトをご紹介します。 下記を参考にし、自らの実現したい動作を作れるようになりましょう。 全ての要素を取得するListの全ての要素を抽出することも、メソッドを複数組み合わせることで実現することができます。 今回使うメソッドはnew listとforeachになります。
List List1 = new List(1,2,3,4,5); //foreach(型 in リスト名)で型に当てはまる全ての要素を指定 foreach(int chr in List1) { //抽出した値を返す Console.WriteLine("{0}", int); }
実行結果はもちろん1,2,3,4,5となります。 末尾の要素を取得したい場合にcountとn番目の要素取得を組み合わせて実現します。 List1でのスクリプトは以下になります。
string str = List1(List1.Count - 1 );
list.Countで要素数を抽出し、要素数-1が末尾の要素になるので、list.Count-1としています。 まとめいかがでしたでしょうか。 今回はC#のListについてご紹介してきました。 「どのくらいの値を管理するかわからないから、保存する値の数を定義できない」という時に配列の代わりにListを使ってください。 Listの基本的なメソッドは全て記載しているので、本記事をリファレンス代わりに使用していただけると幸いです。 他の回の記事について気になる方はこちらをクリック |