- 投稿日:2019-04-15T23:18:21+09:00
【C#】yield returnで、遅延評価されるコレクションを作る
やりたいこと
yield returnとかいうわけのわからないreturnが出てきた。
ちょっと調べると、LINQとも、かかわりが深いらしい。どういうものなのか、知りたい。yield returnはどういうものか?
yield returnを使うと、「コレクション」を簡単に作れるのがよいところらしい。ざっとまとめると、
歴史
- コレクションは、「foreach (string str in StringList) {}」みたいな書き方ができて便利だが、これ(コレクション)を作るのは結構大変だった(らしい)。
- それに対して、C# 2.0から、簡単にコレクションを作れる「イテレーター構文」というのが追加された。それが、yield returnを使う構文。
使い方や使う際の制約など
- yield returnやyield breakを使うと、foreach文で使えるコレクションを返すメソッドやプロパティを簡単に実装することができる。
- yield returnを含むブロックを「イテレーター ブロック」という。
- イテレータブロックは、戻り値の型が以下のうちのいずれかにする必要がある。
- System.Collections.IEnumerator
- System.Collections.Generic.IEnumerator
- System.Collections.IEnumerable
- System.Collections.Generic.IEnumerable
- return の変わりに yield return というキーワードを使う。
- break の変わりに yield break というキーワードを使う。
その他、特筆点
- yield returnを使ったイテレータブロックで作成されるコレクションは、「遅延評価」される。
- 「遅延評価」とは、必要になった要素から、必要になった分だけ計算するということ。式が書かれたところではなく、イテレータブロックで作成するコレクションを実際に使うところで、処理が走る。
- 具体的には、下記を参照。
yield returnでコレクションを作る
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApp3 { class Program { static void Main(string[] args) { // 1回目 foreach (string name in GetFriendsNames()) { Console.WriteLine("名前は"); Console.WriteLine(name); } Console.WriteLine("---"); // 2回目 IEnumerable<string> names = GetFriendsNames(); foreach (string name in names) { Console.WriteLine("もう一度、名前は"); Console.WriteLine(name); } Console.WriteLine("---"); // 3回目 foreach (string name in names) { Console.WriteLine("三度、名前は"); Console.WriteLine(name); } Console.ReadKey(); } /// <summary> /// イテレーターブロック /// </summary> private static IEnumerable<string> GetFriendsNames() { Console.WriteLine("john"); yield return "ジョン"; Console.WriteLine("paul"); yield return "ポール"; Console.WriteLine("george"); yield return "ジョージ"; Console.WriteLine("ringo"); yield return "リンゴ"; } } }イテレータブロックの基本動作
下記のイテレータブロックの結果として、"ジョン""ポール""ジョージ""リンゴ"の4つの文字列を含むコレクションができあがる。
private static IEnumerable<string> GetFriendsNames() { Console.WriteLine("john"); yield return "ジョン"; Console.WriteLine("paul"); yield return "ポール"; Console.WriteLine("george"); yield return "ジョージ"; Console.WriteLine("ringo"); yield return "リンゴ"; }コレクションのできるタイミング
コレクションを作成するGetFriendsNames()が実行されるのは、
「IEnumerable names = GetFriendsNames();」の行ではなく
コレクションを使う「foreach (string name in names)」の行。// 2回目 IEnumerable<string> names = GetFriendsNames(); foreach (string name in names) { Console.WriteLine("もう一度、名前は"); Console.WriteLine(name); }しかも、
GetFriendsNames()のメソッドをまず上から下まで実行してコレクションを作ってから、foreachでそれを使う、ではなく、
- GetFriendsNames()のメソッドで、コレクションの要素1個目の"ジョン"を作成(yield return)する
- foreachの1週目を実施
- 次にコレクションの要素2個目の"ポール"を作成
- foreachの2週目を実施
- ・・・・
という感じで、「必要になった要素から、必要になった分だけ計算する」ということをする。
また、結果から分かるように、「必要な時に、毎回同じ計算を行う」ことになっている。
上の例だと、GetFriendsNames()で作るコレクションを使う箇所が来るたびに、毎回GetFriendsNames()を実行することになる。同じことを、Listをnewして行う
同じようなこと(コレクションを作成して、foreachで回すこと)を、Listをnewして行うと、下記のようになる。
// Listを生成 var nameList = new List<string>(); nameList.Add("ミック・ジャガー"); nameList.Add("キース・リチャーズ"); foreach (string name in nameList) { Console.WriteLine("ストーンズ"); Console.WriteLine(name); }このときは、Listの実態を「var nameList = new List();
の行で作成しているため、ヒープにこのList用のメモリを確保している。
そうすると、メモリを食う代わりに、例えば同じforeachを2回書いたとしても、同じ計算は2回行われない。// Listを生成 var nameList = new List<string>(); nameList.Add("ミック・ジャガー"); nameList.Add("キース・リチャーズ"); foreach (string name in nameList) { Console.WriteLine(name); } foreach (string name in nameList) { // ↑すでにメモリ上にあるnameListを使うので、再度リスト作成することはない Console.WriteLine(name); }遅延評価とそうでないものの違い
イテレータブロック(yield returnを使い、遅延評価されるもの)と通常のコレクション作成(遅延評価されないもの)の違いは、まとめると下記。
種類 処理(計算)実施タイミング 処理回数 メモリ使用 イメージ イテレータブロックによるコレクション(遅延評価) 作成するコレクションを使用するとき 使用時毎回 確保しない メモリは食わないが、作ったコレクションを何回も使うときはCPUめっちゃ使う 通常のリスト(遅延評価なし) コレクションを作成するとき 作成時一回 ヒープ上に確保 最初一回リスト作ればそれで作成終わるが、リストが大きいとめっちゃメモリ食う LINQと、遅延評価したくないときの.ToArray()
LINQは、このyield return(遅延評価)で作られている。
なので、上のストーンズのコレクションに対して、// まったく意味ないが var mick = nameList .Where(x => x == "ミック・ジャガー"); Console.WriteLine(mick.First());などとしても、「var mick = nameList.Where・・」の行の時点では、メモリ上にリストはない。
これを、もし「var mick = nameList.Where・・」の時点でListとしてメモリに保持しておきたい、となった場合は、下記のようにする。
var mick = nameList .Where(x => x == "ミック・ジャガー") .ToArray();こうすると、この行の時点でWhereの処理が実際に実行され、メモリにリストが保持される。
その他
上の例では、yiled returnを、メソッドの中で上から何個も並べているが、普通はforなどのループの中で使われると思われる。
参考
- 投稿日:2019-04-15T22:40:51+09:00
UnityのWebGLで外部JavaScriptライブラリを使う
概要
UnityのWebGLプラットフォームで、FirebaseやStripeといった外部サービスのJavaScriptライブラリを使いたかったので、そのときに必要だった手順をまとめました。
WebGLTemplatesの用意
公式ページにあるように
Assets/WebGLTemplates/テンプレート名/
に以下のファイルを作成します。
使いたいライブラリも記述します。index.html<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unity WebGL Player | %UNITY_WEB_NAME%</title> <!--使いたいライブラリを追加--> <script src="%UNITY_WEBGL_LOADER_URL%"></script> <script> var gameInstance = UnityLoader.instantiate("gameContainer", "%UNITY_WEBGL_BUILD_URL%"); </script> </head> <body> <div id="gameContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div> </body> </html>C#←→JavaScriptの連携について
C#とブラウザのJavaScriptと連携する方法も公式ページにあります。
軽く手順説明します。C#→JavaScript
Assets/Plugins/
以下にこのようなファイルを作成します。test.jslibmergeInto(LibraryManager.library, { Hello: function () { //外部ライブラリを使った処理 }, });そして以下のようなコードを書き、WebGLでビルドすると
C#からtest.jslibのHello関数を通して、外部ライブラリの処理呼ぶことができます。NewBehaviourScript.csusing UnityEngine; using System.Runtime.InteropServices; public class NewBehaviourScript : MonoBehaviour { [DllImport("__Internal")] private static extern void Hello(); void Start() { Hello(); } }JavaScript→C#
話が脱線しますがこちらも
WebGLTemplatesのindex.htmlでこのように書きます。index.html<!DOCTYPE html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unity WebGL Player | %UNITY_WEB_NAME%</title> <script src="%UNITY_WEBGL_LOADER_URL%"></script> <script> var gameInstance = UnityLoader.instantiate("gameContainer", "%UNITY_WEBGL_BUILD_URL%"); gameInstance.SendMessage('MyGameObject', 'MyFunction', 5); </script> </head> <body> <div id="gameContainer" style="width: %UNITY_WIDTH%px; height: %UNITY_HEIGHT%px; margin: auto"></div> </body> </html>公式ページに明記されてないと思いますが、以下のように書く必要がありました。
gameInstance.SendMessage('MyGameObject', 'MyFunction', 5)ちょっと工夫
jslibにコードを書いて動作確認をするには、毎回プロジェクトをビルドする必要があります。(よね?)
結構時間がかかって大変だったので、私はWebGLTemplatesにJavaScriptファイルを作成して
ビルド後に一緒に吐き出されるそのファイルを編集して確認していました。また、index.htmlにボタンを追加し、jslibからは実行せずに、そのボタンによってJavaScriptの処理が実行されるようにしました
動作的には
- シーンに応じてC#→jslibでボタンを有効化無効化 (タイトル画面でログインボタンを有効にするとか)
- ボタンでJavaScriptを実行
- JavaScriptの処理結果をC#に渡す
という感じです。
記事を書いたときの環境
Unity 2018 3.8f1
- 投稿日:2019-04-15T19:56:23+09:00
Visual Studio プロジェクトを不要なビルドせずデバッグする
Visual Studioのソリューションが大きくなると、デバッグ実行のとき、いちいち、ビルドが走ってしまって、遅く感じてしまうことはありませんか?
決定論的アセンブリを生成するように変更することで、コード変更がない場合、コンパイルがスキップされ、効率的にデバッグできます。
<Project> <PropertyGroup> <Deterministic>true</Deterministic> </PropertyGroup> </Project>Visual Studio 2019のプロジェクトテンプレートからは、デフォルトで
true
に設定されています。参考:
docs.microsoft.com / Docs / .NET / C# のガイド / 言語リファレンス / コンパイラ オプション / -deterministic
- 投稿日:2019-04-15T18:12:00+09:00
Azure FunctionsでMeCabを実行する例
はじめに
サーバレスアーキテクチャでアプリを作る中で、Azure Functions上にてMecabを実行するにあたりちょっと面倒だったので手段について投稿します。
Mecabとは
ググればいくらでもありますがとりあえず以下参照。
【技術解説】形態素解析とは?MeCabインストール手順からPythonでの実行例までざっくりやりたいこと
- Azure Functions上でMecabを動かしたい
- システム辞書は頻繁に更新しないので埋め込む
- ユーザー辞書は適度に追加したいので外部から読み込みたい
ざっくり仕組み
Mecabの辞書はファイルパスを指定する方式なのでTMPに書き出す必要がありました。
- ユーザー辞書はAzure Storageに置いておく
- Logic Appsの繰り返しトリガーで発動(別にトリガーは何でも良い)
- Logic AppsのBLOBコンテンツの取得アクションを用いてAzure Functionsにつなげる
(Azure Functions上から取得しても良いけど柔軟性がなくなる気がする)- base64で入力される文字列をファイルで生成してTMPに格納
- 埋め込みリソースからsys.dic(etc)を取得しファイルを生成してTMPに格納
TMPに置いたら毎回置き直す必要あるんじゃない?
にて挙動を調査していた記事がありました。(以下引用)
インスタンス間では共有されない
kuduのdebug consoleでは関数で作ったtemp fileは見えない
再起動すれば消える
同一Function App内であれば他関数とTMPディレクトリは共通(他関数が作ったファイルが参照できる)
再起動しなければ結構残ってる(いつ消えるか検証中。再デプロイくらいでは消えない)
容量は500MBまで。実際に初回の起動は25秒程度かかりましたが即座に二回目を起動すると4秒程度でした。
もちろん、TMPに存在していたらファイルを生成しない、とかしてます。
ユーザー辞書は毎回置き直してます。ユーザー辞書毎回HttpRequestに載せたらお金かかるんじゃない?
そこはなんともです。頻度とかユーザー辞書の大きさによるかなと思っています。
- WebAppとしてリリース
- コンテナ化してリリース
- 埋め込みリソースにしてユーザー辞書更新の度にAzure Functions再配置
等々いくつか手段は考えられます。
まとめ
他に良い方法ありましたらご教示願います。
ソース例
githubに上げたいんですけどまだ整ってないので抜粋です。必要なところだけ抜いただけでこの状態で動かしていないので動かなかったらごめんなさい。
環境
Azure_Functions抜粋public static class Function1 { [FunctionName("Function1")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JObject.Parse(requestBody); string userdicpath = ConvertUserDic(data); await Program.Main(new[] { (string)data.exec, "-userdic", userdicpath }); return (ActionResult)new OkObjectResult("OK"); } private static string ConvertUserDic(dynamic data) { if ((data is JObject j) && (j["userdic"]["$content"].Value<string>() is string s)) { var dic = System.IO.Directory.CreateDirectory(Path.Combine(Environment.GetEnvironmentVariable("TMP"), "dic")); var userdic = Path.Combine(dic.FullName, "user.dic"); using (var stream = new MemoryStream(System.Convert.FromBase64String(s))) { using (var file = File.Create(userdic)) { stream.CopyTo(file); file.Close(); } } return userdic; } else { throw new ArgumentException("userdic", data?.userdic); } } }Program例public class Program { public static async Task Main(string[] args) { await BatchHost.CreateDefaultBuilder().RunBatchEngineAsync(args); } }という風に埋め込みリソースがあるとして
NMecab例public class MecabBatch : BatchBase { private ILogger<MecabBatch> Logger { get; } private string DicPath { get; } public MecabBatch(ILogger<MecabBatch> _logger) { Logger = _logger; DicPath = Path.Combine(Environment.GetEnvironmentVariable("TMP"), "dic"); Task.WaitAll(new[] { CreateFileAsync(DicPath, "char.bin"), CreateFileAsync(DicPath, "dicrc"), CreateFileAsync(DicPath, "matrix.bin"), CreateFileAsync(DicPath, "sys.dic"), CreateFileAsync(DicPath, "unk.dic"), }); } public void Execute( [Option("userdic", "user dictionary path.")]string userdic ) { using (var m = MeCabTagger.Create( new MeCabParam { DicDir = DicPath, UserDic = new[] { userdic }, } )) { getMeCabNode(m.ParseToNode("本日も晴天なり")) .Where(n => String.IsNullOrEmpty(n.Feature) == false) .ToList() .ForEach(n => Logger.LogInformation(n.Feature)); } } private async Task CreateFileAsync(string dicPath, string fileName) { if (File.Exists(Path.Combine(dicPath, fileName))) return; Directory.CreateDirectory(dicPath); var assembly = Assembly.GetExecutingAssembly(); using (var stream = assembly.GetManifestResourceStream("MecabImpl.dic." + fileName)) { using (var file = File.Create(Path.Combine(dicPath, fileName))) { await stream.CopyToAsync(file); file.Close(); } } } private IEnumerable<MeCabNode> getMeCabNode(MeCabNode n) { yield return n; while (n.Next != null) { n = n.Next; yield return n; } } }したら
名詞,副詞可能,*,*,*,*,本日,ホンジツ,ホンジツ 助詞,係助詞,*,*,*,*,も,モ,モ 名詞,一般,*,*,*,*,晴天,セイテン,セイテン 助動詞,*,*,*,文語・ナリ,基本形,なり,ナリ,ナリとできました。
- 投稿日:2019-04-15T16:05:49+09:00
情報研修_05_配列と探索アルゴリズム_20190415(未完成)
0. 前書き
0.1 配列についての説明
0.2 探索アルゴリズム
- 順次探索法
- 二分探索法
1. 配列に関するまとめ
2. 探索に関するまとめ
3. 練習問題
3.1 問4
Lesson04.slnusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Lesson04 { class Program { static void Main(string[] args) { int[] X = new int[] {3, 6, 0, 5, 1}; int i = 0; int j = 0; while (i < X.Length) { Console.Write(X[i] + " "); while (X[i] > j) { Console.Write(j); j++; } j = 0; Console.WriteLine(); i++; } Console.ReadLine(); } } }3.2 問5
Lesson05.slnusing System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Lesson05 { class Program { static void Main(string[] args) { int[] X = { 1002, 1005, 1010, 1024, 1028, 1052, 1211, 1322, 1866, 2132 }; Console.Write("調べる数値を入力:"); int NUM_search = int.Parse(Console.ReadLine()); int i = X.Length / 2; if (NUM_search == X[i]) { Console.WriteLine("探索成功!"); Console.Write("調べる数は配列の{0}番目である。", i+1); Console.ReadLine(); return; } else if (NUM_search > X[i]) { do { i++; if (i == X.Length) { break; } if (NUM_search == X[i]) { Console.WriteLine("探索成功!"); Console.Write("調べる数は配列の{0}番目である。", i + 1); Console.ReadLine(); return; } } while (i < X.Length); } else { do { i--; if (i == 0) { break; } if (NUM_search == X[i]) { Console.WriteLine("探索成功!"); Console.Write("調べる数は配列の{0}番目である。", i + 1); Console.ReadLine(); return; } } while (i < X.Length); } Console.Write("探索失敗、お探しの数値が配列に存在しない。"); Console.ReadLine(); } } }
- 投稿日:2019-04-15T10:54:32+09:00
C# ファイル読み込み(1行ずつの処理)
初心者様への前置き
プログラミング初心者の方,はろーわーるどや足し引き算してて楽しいですか?
何か達成感を得られましたか?
実際に自分のやってる事をプログラムで処理しないと,実感湧かなくないですか?
という事で,実際にファイル処理をすれば実感が湧きそうなのでやりました!ファイル読み込みの基本的なやり方
using System; // ファイル処理に使用 using System.IO; // 文字のエンコードに使用(utf-8なら書かなくて大丈夫です) using System.Text; class test { // shift-jisの場合: var file = new StreamReader(path, Encoding.GetEncoding("shift-jis")); var 変数名 = new StreamReader("ファイルのパス",Encoding.GetEncoding("望む書式")); // utf-8の場合: var file = new StreamReader(path); var 変数名 = new StreamReade("ファイルのパス") }はい,こんな感じで指定してあげます.
で,次は実際にファイルの処理ですね.
// lineはstring型の変数です. while ((line = file.ReadLine()) != null) { Console.WriteLine(line); } while((変数=file.ReadLine()) != nu;;) { 処理 }こんな感じですね〜
で,ここからは私事なんですけど,lineは,区切りのcsvファイルから持ってきたものだったので,その処理もついでに書きました.// dataはlineと読み替えてくれて構いません. for(var k = 0; k < data.Length;k++) { var result = data[k]; // Console.WriteLine(res); try { var vod = result.Trim().Split(','); Console.WriteLine(vod[0]+vod[1]+vod[2]+vod[4]); } catch { continue; } }まぁ無駄な{}や冗長な部分もありますが初心者ですので,大目に見ていただけると幸いです.
ちなみに,このカンマの処理技術はPaizaさんなどの競技プログラミングではひっすのてくにっくですので,少しでも力になったのなら幸いです.
- 投稿日:2019-04-15T10:54:32+09:00
C# ファイル入出力(1行ずつの処理)
初心者様への前置き
プログラミング初心者の方,はろーわーるどや足し引き算してて楽しいですか?
何か達成感を得られましたか?
実際に自分のやってる事をプログラムで処理しないと,実感湧かなくないですか?
という事で,実際にファイル処理をすれば実感が湧きそうなのでやりました!ファイル読み込みの基本的なやり方
using System; // ファイル処理に使用 using System.IO; // 文字のエンコードに使用(utf-8なら書かなくて大丈夫です) using System.Text; class test { // shift-jisの場合: var file = new StreamReader(path, Encoding.GetEncoding("shift-jis")); var 変数名 = new StreamReader("ファイルのパス",Encoding.GetEncoding("望むエンコード形式")); // utf-8の場合: var file = new StreamReader(path); var 変数名 = new StreamReader("ファイルのパス") }はい,こんな感じで指定してあげます.
で,次は実際にファイルの処理ですね.
// lineはstring型の変数です. while ((line = file.ReadLine()) != null) { Console.WriteLine(line); } while((変数=file.ReadLine()) != null) { 処理 }で,次はファイルの書き込みですね.
まぁ大体は,ファイル読み込みと大差ないような気がします.// 追記の場合 using(var writer = new StreamWriter(path,true)) { writer.WriteLine("ファイルの書き込みに成功しました"); writer.WriteLine("書き込みたい内容"); // ちゃんと書くなら: try { writer.WriteLine("test"); } catch { Console.WriteLine("ファイルの書き込みでエラーが発生しました."); } }って感じです.一応,成功しました,失敗しましたなどのメッセージを出しとけばわかりやすいと思われます.
それと,これはutf-8の場合ですのでshift-jisにしたいならusingの最後の引数に,shift-jisを指定してあげてください.
あい、次へ!// 今までの内容を全て消して,書き直す(上書き保存する)場合: using(var writer = new StreamWriter(path, false)) { writer.WriteLine("さくせす"); }こんな感じで,引数にfalseを指定してあげるだけですね〜
ファイルの入出力は以上です!
で,ここからは私事でファイル読み込みからのつづきなんですが ,lineは,区切りのcsvファイルから持ってきたものだったので,その処理もついでに書きました.// dataはlineと読み替えてくれて構いません. for(var k = 0; k < data.Length;k++) { var result = data[k]; // Console.WriteLine(res); try { var vod = result.Trim().Split(','); Console.WriteLine(vod[0]+vod[1]+vod[2]+vod[4]); } catch { continue; } }まぁ無駄な{}や冗長な部分もありますが初心者ですので,大目に見ていただけると幸いです.
ちなみに,このカンマの処理技術はPaizaさんなどの競技プログラミングでは必須のテクニックですので,少しでも力になったのなら幸いです.
稚拙な点やツッコミどころも多々あると思われます.
何かありましたら,気軽にコメントください!
- 投稿日:2019-04-15T10:06:29+09:00
Actionを1回だけ実行できるようにしたい
概要
コールバックを別のオブジェクトに渡して呼び出してもらうようなときに、複数回呼んで欲しくない(1回呼び出したらコールバックそのものが消滅してほしい)場合に利用できるラッパークラスを作りました。
参考
1回だけ実行可能なコールバッククラス
/// <summary> /// 1度だけ実行可能なAction /// </summary> public class OnceAction { private Action _Action; /// <summary> /// 空オブジェクト /// </summary> public static OnceAction Default = new OnceAction(); /// <summary> /// 実行可能であればtrue /// </summary> public bool IsEnable => this._Action != null; /// <summary> /// 1度だけ実行可能なAction /// </summary> private OnceAction() { this._Action = null; } /// <summary> /// 1度だけ実行可能なAction /// </summary> public OnceAction(Action action) { this._Action = action; } /// <summary> /// 登録されたコールバックを呼び出す /// </summary> [MethodImpl(MethodImplOptions.Synchronized)] public void Invoke() { if (this._Action == null) { return; } this._Action.Invoke(); this._Action = null; } }なんてことはない、1回呼び出したらActionへの参照を消してやるだけの簡単なラッパークラスです。
ですがActionの生成側で使用回数を制限できるのが便利です。利用側で呼び出した回数を管理したりActionへの参照を消したりするの面倒ですからね。
OnceAction.Default
は、OnceAction型の変数なりプロパティなりの初期値として利用します。Actionの場合は以下のように書けば
nullアクセス違反にはならないが呼び出しても何も起きない
状態にできました。// こんな感じで初期化しておくと呼び出すときにnullチェック不用 private Action _Callback = delegate { };しかし、OnceActionではいちいちインスタンスをnewで作らないといけなくなるので、その代わりとして作りました。
// こんな感じで初期化しておくとnullチェック不用 private OnceAction _Callback = OnceAction.Default;