複数の値から最小値や最大値を求める簡単な方法はないの?
Mathf.Min/Maxメソッドを使えば良いわ。
複数の値の集合から最大値・最小値を求める方法の解説です。
Unity APIが使える環境なら、Mathf.Min、Mathf.Maxメソッドからそれぞれ最小値、最大値を求めることができます。
引数には複数個の値を指定できますが、3個以上の値を引数として指定するとGC.Allocが発生します。 パフォーマンス低下の要因となるため、注意が必要です。
参考:Unity – Manual: Managed memory
この問題は、自作のラッパーメソッドなどを用いれば簡単に解決できます。
また、次のようにListなどのコレクションから最小値と最大値を求めることも可能です。
このように、最小値・最大値を求める方法は複数存在し、状況に応じて使い分けることができます。
本記事では、複数個の値から最小値や最大値を求める方法をいくつか紹介します。
- Unity 2022.1.0f1
目次 非表示
最小値を求める
Mathf.Minメソッドを使います。複数のfloat型またはint型の値から最小値を求めることができます。
メソッドの形式は次の通りです。
float型の場合
int型の場合
引数が2つとそれ以外、int型とfloat型でメソッドがオーバーロードされています。
3個以上の引数を渡す場合、可変長引数となり、配列として渡す形になります。
参考:Mathf-Min – Unity スクリプトリファレンス
最大値を求める
Mathf.Maxメソッドを使います。引数の形式などはMathf.Minと一緒です。複数のfloat型またはint型の値から最大値を求めます。
float型の場合
int型の場合
参考:Mathf-Max – Unity スクリプトリファレンス
サンプルコード
複数の指定された値から最大値、最小値を求めるサンプルスクリプトです。
このスクリプトをMinMaxExample.csという名前でUnityプロジェクトに保存し、適当なオブジェクトにアタッチすると機能するようになります。
インスペクターよりInput Valuesに入力値を複数指定できます。
実行結果
入力値とその結果です。
入力値
出力結果
3個以上の引数を渡す場合の注意点
Mathf.Min/Maxメソッドは、複数個の値を引数として渡せること解説しました。
しかし、ここに一つ落とし穴があります。それは、3個以上の値を引数として渡すと、メモリ領域の確保(AC.Alloc)が必ず発生してしまう点です。
次にその検証用コードと結果を示します。
検証用コード
3つの値から最大値を毎フレーム求めるだけのサンプルスクリプトです。
プロファイラでの検証結果
プロファイラを見ると、上記スクリプトで毎フレーム44ByteのGC.Allocが発生しています。
これは、複数の引数を渡す際に新しい配列を確保しているためです。
意図せずメモリ確保が行われ続けた場合、処理によってはメモリの断片化と共にマネージヒープが拡張され続ける可能性があります。詳細については必ず公式リファレンス等の一次情報をご確認の上判断してください。
参考:Unity – Manual: Managed memory
3引数以上でもGC.Allocを発生させないようにする
GC.Allocを回避する手段はいくつか存在しますが、あまり多くない個数であれば、独自のラッパーを作れば簡単に解消できます。
ラッパーの例
3引数から最小値・最大値を求めるメソッドを実装したラッパーの例です。
例ではfloat型や3引数以外のものは割愛していますが、同じ要領で実装していけば良いです。
使用例
呼び出し側では、Mathf構造体をMathUtil構造体 置き換えるだけで使えるようになります。
に配列を直接指定する場合
引数に配列をそのまま渡してもGC.Allocを抑えられます。
修正後の結果
GC.Allocがなくなっていることが確認できました。
Listなど配列以外のコレクションの最小値・最大値を求める
List<T>型や、IEnumerable<T>型など、配列(Array<T>型)以外のコレクションから最小値、最大値を求めたい場合は、Mathf.Min/Maxメソッドが使えません。
これを可能にするため、本記事では以下の2つの方法を紹介します。
- Linqを使う(ただしGC.Allocが発生する)
- 独自実装する(List型など一部の型ではGC.Allocの発生を抑えられる)
前者のLinqを使う方法は手軽な分、算出処理を行う度に必ずGC.Allocが発生してしまいます。しかしながら、簡潔なコードになるのが大きな魅力です。
後者の独自実装する方法は、手間はかかりますがGC.Allocを抑えられる場合があります。しかし、IEnumerable<T>などインタフェース化された型に対してはGC.Allocを抑えることができません。
Linqを使った例
Linqを使って最小値・最大値を求めるサンプルスクリプトです。
出力結果は1つ目のスクリプトと一緒のため、割愛いたします。
次のように、Min/Max拡張メソッド呼び出しで1行で結果を得られます。
GC.Allocが必ず発生するため、注意しておくと良いでしょう。
独自実装する例
余計なメモリ確保を解消するために、最小値、最大値を独自実装したサンプルスクリプトです。
1つ目のスクリプトはIList<T>型のコレクションから最小値・最大値を求める拡張メソッドの定義です。今回はint型のみの例を示しましたが、float型などその他の型も同じ要領で実装できます。
2つ目のスクリプトは1つめのスクリプトの拡張メソッドを使用した例です。
こちらも出力結果を割愛しますが、GC.Allocの発生が解消されています。
さいごに
複数の値の最小値・最大値はMathf.Min/Maxメソッドで求められます。
List型など配列以外のコレクションに対しての最小値・最大値を求める方法も紹介しました。
Mathf.Min/Maxメソッドは、3引数以上の場合は可変長引数扱いとなりGC.Allocが発生するため、パフォーマンスを求める場面では注意が必要です。
ラッパークラスを活用すれば、パフォーマンスと可読性両方の問題を一度に解決でき、実装も難しくありません。
どの方法が適切かは場面によって変わってくるため、それぞれの特徴を理解した上で使い分けてください。