これまでC#の安定ソートが欲しくて色々と検討していました。
アロケーションを気にしなければ、LinkedListについては各要素にインデックス振ったListを作ってSort()するのが速いという結果が出ていました。その実装をここにまとめます。List版もありますが、先の記事で検討したマージソートの方が速いので、Listではそっちを使ったほうがいいです。性能比較については前回の記事を参照。
これまでC#の安定ソートが欲しくて色々と検討していました。
アロケーションを気にしなければ、LinkedListについては各要素にインデックス振ったListを作ってSort()するのが速いという結果が出ていました。その実装をここにまとめます。List版もありますが、先の記事で検討したマージソートの方が速いので、Listではそっちを使ったほうがいいです。性能比較については前回の記事を参照。
(2017/09/10 パフォーマンス比較にOrderByを追加。それに伴い、結論をやや修正)
ListとLinkedListの挿入ソートおよびマージソートを実装します。どちらも安定ソートです。
何を考えて実装したのか、長々と別の記事に書いてあるので、興味があればそっちもよろしくです。
マージソートを単純に実装するより、対象となる要素数が少ない時は挿入ソートのほうが速いので、挿入ソートに差し替える対応をします。なので、マージソートの実装がメインで、最適化の副産物で挿入ソートもあるよ、という感じです。
Unityでゲームプログラムを書いていると、C#で気軽に使える安定ソートが欲しくなるのですが、これという機能が標準で用意されていません。
今更ソートなんて自前で実装したくないんですが、欲しいものがなければ作るしかない。実装までに検討した色々なことをこの記事ではまとめていきます。
実装したものは別の記事にまとめたので、そちらをどうぞ。
アルゴリズム系の実装コードって何故かほとんど可読性が低いですよね。主に変数名がやっつけすぎて理解に時間がかかります。バグの温床だと思うので、そのへんも私なりに読みやすくしています。
その点、これからいくらか紹介する.NETやMonoの実装はとても綺麗で理解しやすいです。
Unityで外部ファイルにデータを読み書きするとき、普通Application.persistentDataPathを使うと思うのですが、AndroidだけpersistentDataPathの返すパスが権限によって変わります。
これドキュメントに書かなきゃいけない重要事項だと思うんですが、全く触れられていない・・・。
詳しいことはこれから説明していきますが、やりたいことによっては必要な追加権限があるにも関わらず、それを設定したときには、persistentDataPathはセーブデータの保存先として信用できないという衝撃的な問題が存在します。Unityのバージョンは5.3.4f1です。
但し、Unity 5.3.6でpersistentDataPathおよびtemporaryCachePathがAndroid 4.4(コードネーム:KitKat、API Level 19)以降、「外部」を指すよう仕様変更となったそうです(リリースノート)。また混乱することを・・・。いずれにせよ、AndroidでpersistentDataPathを利用することは推奨しません。
以下、死ぬほど長いまとめ書きましたので、覚悟してください。
引き続きUnityの記事を書き溜めていたので放出。
Unityでシリアライズ可能なコレクションは、Listだけです。但し、当然ですが、型引数もシリアライズ可能型を指定してやる前提です。
C#のListはいわゆる配列リストってやつで、C++でいうvector、JavaでいうArrayListに相当します。C#にもArrayListは存在しますが、古い機能のため非ジェネリックです。
ともかく、Listは内部実装として配列を持つため、List<T>
ならば、T型配列としてUnityがシリアライズしてくれるわけですね。Listだけシリアライズ可能なのはまあ納得です。
とはいえ、Listだけで事足りるかというと、LinkedList(連結リスト、線形リスト)やDictionary(ハッシュマップ)あたりは欲しい感じです。
ScriptableObjectやJsonUtilityでシリアライズ・デシリアライズするとき、ISerializationCallbackReceiverを実装していると、シリアライズ前、デシリアライズ後に決まったメソッドが呼ばれます。これを利用してコレクションをListに変換してシリアライズできます。公式ドキュメントでもこれを利用してDictionaryをシリアライズ・デシリアライズするサンプルが掲載されています。
※OnBeforeSerializeとOnAfterDeserializeを実装しますが、何故かOnBeforeSerializeに解説が集中しているのでそっちのリンクを掲載。
この場合、インスペクタを介したリアルタイムな編集はできませんが、永続化が目的ならばこれで十分でしょう。LinkedListやDictionaryをインスペクタで触れるようにするとむしろ怖い。
そんなこんなで、Unityで使用可能なC#のコレクションをISerializationCallbackReceiver実装クラスで一通りラップしてみたので、以下に公開します。
(2017/10/14 EnumUtilの初期化を静的コンストラクタに変更。何故最初からそうしなかったのか)
なんだかんだUnityとの付き合いが多いので、メモがてらあんまりネット上で見なかったり見つけにくかったりする情報をまとめる感じで。
今回はenum型(列挙型、列挙体)のシリアライズについての話です。前回「C#のenumの使い方」の続きでもあります。本当は同じ日に仕上げるつもりでしたが、長くなったので分離しました。enumそのものの使い方は前回の記事にまとめておいたので、その知識は前提とします。
仕事が忙しすぎて書きたかったネタが全然まとまらずに、技術関連の文章がまとまっていくアレ。
C#でのenumの使い方を簡単にまとめ。
私はWPFよりかは基本的にUnityでC#使ってます。Debug.Log()はUnityでのコンソール出力です。
public enum Type
{
Invalid,
A,
B,
C,
}
特に断りなければ、こんな定義のenum使ってると思ってください。
C#のenumは内部的には整数型です。値の割り当てを省略すれば、先頭では0が割り当てられ、以降は直前の定義に+1した値となります。C/C++でもおなじみのよくあるルールです。Javaはすっこんでいてください。
公式ドキュメントはこのあたり。言語機能そのものの説明はあまりしないので、詳しく知りたかったら自分で調べてねという感じで。