- 投稿日:2020-03-29T23:59:19+09:00
C# 自作クラスListのコピー(Deep Copy)
コピーは1種類じゃない?
プログラミングにおける変数のコピーには
・ディープコピー
・シャローコピー
の2種類があります。
それぞれを一言でいうとディープコピー:コピー先の変数を変更しても、コピー元には影響がない
ディープコピーの例int a = 1; int b = a; b = 2; Console.WriteLine("a={0}",a); Console.WriteLine("b={0}",b);実行結果a=1 b=2上の例では、bを変えてもaには影響が出ていません
シャローコピー:コピー先の変数を変更すると、コピー元にも変更が適用される
シャローコピーの例List<int> a = new List<int> { 0, 0, 0}; List<int> b = a; b[1] = 1; Console.Write("a="); foreach(var member in a) Console.Write("{0} ",member); Console.Write("\nb="); foreach(var member in b) Console.Write("{0} ",member);実行結果a=0 1 0 b=0 1 0bを変えると、aも変わってしまっています。
C#におけるコピー
上の例を見て頂ければわかる通り、C#においては「=」で代入すると
int、doubleなどの値型:ディープコピー
配列、Listなどの参照型:シャローコピー
となるようです。そもそものコピーの目的として
「元のデータを変えたくないからコピーしてるんだ!」
という理由が相当数を占めていると思うので、
基本的には
ディープコピーの需要が高い
と思っています。
しかしC#の標準ライブラリには汎用的にディープコピーするメソッドがないので、
実装してみました。
2通りの方法で実装しています。実装例1:シリアライズを利用
こちらを参考にさせて頂きました。
参考というよりほぼコピペです。
ディープなコピペをしてしまい申し訳ない、、ディープコピー用拡張メソッドpublic static T DeepClone<T>(this T src) { using (var memoryStream = new System.IO.MemoryStream()) { var binaryFormatter = new System.Runtime.Serialization .Formatters.Binary.BinaryFormatter(); binaryFormatter.Serialize(memoryStream, src); // シリアライズ memoryStream.Seek(0, System.IO.SeekOrigin.Begin); return (T)binaryFormatter.Deserialize(memoryStream); // デシリアライズ } }検証用の自作クラス([Serializable]の記載を忘れるとエラーが出ます)[Serializable] class originalClass { public int i; public string s; public List<int> iList; }実行例List<originalClass> a = new List<originalClass>{ new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }, new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }}; List<originalClass> b = a.DeepClone(); b[0].i = 1; b[0].iList[0] = 1; Console.Write("a="); foreach (var mem1 in a) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); } Console.Write("\nb="); foreach (var mem1 in b) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); }```:実行結果 a=0 ○ {0 0 } 0 ○ {0 0 } b=1 ○ {1 0 } 0 ○ {0 0 }bを変更してもaは変わっていないので、正常にディープコピーできていそうです。
実装例2:コンストラクタを利用してnew
コンストラクタを適切に定義すれば、newによりディープコピーができるようです。
ただし、多重に参照している場合、値型にたどりつくまで下層を走査する必要があります。
詳しくは下記参考をご参照ください検証用の自作クラス+コンストラクタclass originalClass { public int i; public string s; public List<int> iList; public originalClass(){} public originalClass(originalClass src) { i = src.i; s = src.s; iList = new List<int>(); foreach(var mem in src.iList) iList.Add(mem); } }実行例List<originalClass> a = new List<originalClass>{ new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }, new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }}; List<originalClass> b = new List<originalClass>(); foreach (var mem in a) b.Add(new originalClass(mem)); b[0].i = 1; b[0].iList[0] = 1; Console.Write("a="); foreach (var mem1 in a) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); } Console.Write("\nb="); foreach (var mem1 in b) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); }実行結果a=0 ○ {0 0 } 0 ○ {0 0 } b=1 ○ {1 0 } 0 ○ {0 0 }bを変更してもaは変わっていないので、正常にディープコピーできていそうです。
参考:C#におけるシャローコピーとディープコピー
どんな場合がシャローコピー、どんな場合がディープコピーとなるか調べてみました
上の例を見ると、newすればコンストラクタが勝手にディープコピーしてくれるように見えます。
実行例を下記します。値型のリストの場合
newによるListのディープコピーList<int> a = new List<int> { 0, 0, 0}; List<int> b = new List<int>(a); b[1] = 1; Console.Write("a="); foreach(var member in a) Console.Write("{0} ",member); Console.Write("\nb="); foreach(var member in b) Console.Write("{0} ",member);実行結果a=0 0 0 b=0 1 0bを変更してもaは変わっておらず、ディープコピーされているようです
多重リストの場合
次のような多重リストではどうでしょう?
newによる2重ListのコピーList<List<int>> a = new List<List<int>>{ new List<int> { 0, 0, 0 }, new List<int> { 0, 0, 0 }, new List<int> { 0, 0, 0 } }; List<List<int>> b = new List<List<int>>(a); b[1][1] = 1; Console.Write("a="); foreach (var mem1 in a) { foreach (var mem2 in mem1) Console.Write("{0} ", mem2); Console.Write("\n"); } Console.Write("\nb="); foreach (var mem1 in b) { foreach (var mem2 in mem1) Console.Write("{0} ", mem2); Console.Write("\n"); }実行結果a=0 0 0 0 1 0 0 0 0 b=0 0 0 0 1 0 0 0 0bと一緒にaまで変更されてしまっており、シャローコピーとなっているようです。
直下のリストはディープコピーされるが、そのリストが示す参照先はコピー元と共通、
といった感じと思われます。自作クラスListの場合
データベース的な使い方をよくする、自作クラスListに対して、
よくありそうな3パターンに分けてコンストラクタによるnewの挙動を、
調べたいと思います。newによるコピー結果検証用コードList<originalClass> a = new List<originalClass>{ new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }, new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }}; List<originalClass> b = new List<originalClass>(a); b[0].i = 1; b[0].iList[0] = 1; Console.Write("a="); foreach (var mem1 in a) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); } Console.Write("\nb="); foreach (var mem1 in b) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); }
パターン1:コンストラクタ定義なしクラス定義class originalClass { public int i; public string s; public List<int> iList; }実行結果a=1 ○ {1 0 } 0 ○ {0 0 } b=1 ○ {1 0 } 0 ○ {0 0 }中身が値型だろうが容赦なしでシャローコピーされています
パターン2:コンストラクタ + foreachでコピークラスの定義class originalClass { public int i; public string s; public List<int> iList; public originalClass(){} public originalClass(originalClass src) { i = src.i; s = src.s; iList = src.iList; } }検証用コードの変更(コピー部分のみ抜粋)List<originalClass> a = new List<originalClass>{ new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }, new originalClass{ i = 0, s = "○", iList = new List<int> { 0, 0} }}; List<originalClass> b = new List<originalClass>(); foreach (var mem in a) b.Add(new originalClass(mem)); b[0].i = 1; b[0].iList[0] = 1; Console.Write("a="); foreach (var mem1 in a) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); } Console.Write("\nb="); foreach (var mem1 in b) { Console.Write("{0} ", mem1.i); Console.Write("{0} ", mem1.s); Console.Write("{"); foreach (var mem2 in mem1.iList) Console.Write("{0} ", mem2); Console.Write("}"); Console.Write("\n"); }実行結果a=0 ○ {1 0 } 0 ○ {0 0 } b=1 ○ {1 0 } 0 ○ {0 0 }値型はディープコピー、参照型(List)はシャローコピーされています
パターン3:パターン2 + コンストラクタのリスト部分のみforeachで1要素ずつ代入
クラスの定義class originalClass { public int i; public string s; public List<int> iList; public originalClass(){} public originalClass(originalClass src) { i = src.i; s = src.s; iList = new List<int>(); foreach(var mem in src.iList) iList.Add(mem); } }実行結果a=0 ○ {0 0 } 0 ○ {0 0 } b=1 ○ {1 0 } 0 ○ {0 0 }ついにディープコピーが実現できました(bを変えてもaは変わらない)
このパターン3は、上の実装例2と同じものです。
結論としては、参照型が内部に存在する場合、値型にたどり着くまで内部を辿ってから代入しないといけないみたいです。
複雑な自作クラスになるとどこが値型なのか探索するのも、コンストラクタの実装の手間も大きそうなので、
実装1の方法がシンプルでよさそうです。まとめ:ディープコピーになる場合とシャローコピーになる場合
※自作リストの場合は上記参照ください
C#においてディープコピーとなる例//値型を代入 int b = a; //stringの代入(stringは参照型だが、例外的にディープコピーとなる) string b = a; //値型のListをコンストラクタでnew List<int> b = new List<int>(a);C#においてシャローコピーとなる例//配列、リスト等の参照型を代入 int[] b = a; List<int> b = a; //多重Listをコンストラクタでnew List<List<int>> b = new List<List<int>>(a); //foreachの中身(下の例でmemberを変更すると、aも変更される) foreach(var member in a) //gourpbyの中身(下の例でgroupaを変更すると、aも変更される) var groupa = a.Groupby(c => c.key)foreachやgroupbyは、逆にディープコピーだとループ内での変更ができず困る場面もありそうなので、
シャローコピーで助かる面も多いかと思います。
- 投稿日:2020-03-29T23:48:47+09:00
Sign in with Apple を ASP.NET Core のウェブサイトに導入してみる
前書き
ASP.NET Core で作成したウェブサイトに Sign in WIth Apple を組み込む必要があったので、調べたついでに導入手順を書き残します。
需要があるかわかりませんが、GitHub にサンプルもあげておきます。
https://github.com/Yuki0928/SignInWIthAppleSampleASP .NET Core では Facebook、Twitter、Google、Microsoft などの外部プロバイダーの認証を非常に簡単にアプリに組み込めるのですが、Sign in with Apple は勝手が違うところがあり少し手間でした。
作業環境
Windows 10
Visual Studio 2019Apple 開発者ポータルでの事前準備
アップルのヘルプなどを参考に Sign in with Apple を使用する準備を行います。
https://help.apple.com/developer-account/#/devde676e696主な作業は3つです。
- App ID の作成
- Service ID の作成
- Private Key の作成個人的にはヘルプは少々わかりづらかったので、こちらの記事を参考に準備を行いました。
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-appleドメインと認証後のコールバック URL の登録 について
今回はローカルで動作確認を行うため、example.com と https://example.com:5001/signin-apple を登録し、hosts に 127.0.0.1 example.com のレコードを登録しました。
localhost で登録できればいいんですが、ちょっと面倒ですね。ASP.NET Core Web アプリケーションを作成と動作確認
Visual Studio を起動して ASP.NET Core Web アプリケーション を作成します。
Web アプリケーション(モデル ビュー コントローラー)を選択します。
Properties から LaunchSettings.json を開きます。
下のほうの applicationUrl の localhost を example.com に書き換えます。
IIS Express の横の小さな三角ボタンを押して、SignInWithAppleSample を選択します。
SignInWithAppleSample ボタンを押します。
Windows ファイアウォールの警告が表示された場合はアクセスを許可します。
https://example.com:5001 で ASP.NET Core Web アプリケーションが動いていることが確認できました。このブラウザは一旦閉じます。
Microsoft.AspNetCore.Authentication.OpenIdConnect をインストール
NuGet で Microsoft.AspNetCore.Authentication.OpenIdConnect をインストールします。
TokenGenerator の作成
Model に TokenGenerator クラスを作成します。
TokenGenerator.cs のコードはこちらをコピペして上書きします。
iss、sub、kid、privateKey は自分の情報に書き換えます。(詳しくは後述)using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; namespace SignInWithAppleSample.Models { public static class TokenGenerator { public static string CreateNewToken() { const string iss = "TeamID をいれます"; const string sub = "ServiceID をいれます"; const string kid = "KeyID をいれます"; const string privateKey = "PrivateKey をいれます"; var cngKey = CngKey.Import( Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob); var handler = new JwtSecurityTokenHandler(); var token = handler.CreateJwtSecurityToken( issuer: iss, audience: "https://appleid.apple.com", subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }), expires: DateTime.UtcNow.AddMinutes(5), issuedAt: DateTime.UtcNow, notBefore: DateTime.UtcNow, signingCredentials: new SigningCredentials(new ECDsaSecurityKey(new ECDsaCng(cngKey)) { KeyId = kid }, SecurityAlgorithms.EcdsaSha256)); return handler.WriteToken(token); } } }TeamID と ServiceID は Apple の開発者ポータルから取得します。
KeyID とPrivateKey は開発者ポータルで Key を発行したときに
AuthKey_xxxxxxxxxx.p8
というファイルをダウンロードしているはずですのでそちらから取得します。KeyID は上記のファイル名の xxxxxxxxxx の部分です。
PrybateKey はファイル内の文字列の改行を削除して1行にしたものを入れます。
先頭の-----BEGIN PRIVATE KEY-----
と末尾の-----END PRIVATE KEY-----
も削除してください。Sign in with Apple を組み込む
using を追加します。
using System.Net.Http; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using SignInWithAppleSample.Models;ConfigureServices メソッド を以下のように書き換えます。
ServiceID は先ほどと同じものをいれます。public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect(async options => { options.ClientId = "ServiceID をいれます"; options.CallbackPath = "/signin-apple"; options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.ResponseType = OpenIdConnectResponseType.Code; options.Scope.Clear(); options.Configuration = new OpenIdConnectConfiguration { AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize", TokenEndpoint = "https://appleid.apple.com/auth/token", }; options.Events.OnAuthorizationCodeReceived = context => { context.TokenEndpointRequest.ClientSecret = TokenGenerator.CreateNewToken(); return Task.CompletedTask; }; options.TokenValidationParameters.ValidIssuer = "https://appleid.apple.com"; var jwks = await new HttpClient().GetStringAsync("https://appleid.apple.com/auth/keys"); options.TokenValidationParameters.IssuerSigningKeys = new JsonWebKeySet(jwks).Keys; options.ProtocolValidator.RequireNonce = false; }); }Configure メソッドに
app.UseAuthentication();
を追加します。public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseAuthentication(); // この1行を追加 app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }適当な場所に SignInWithApple メソッドを追加します。
public IActionResult SignInWithApple () { return Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectDefaults.AuthenticationScheme); }末尾に以下のコードを書き足します。
@if (User.Identity.IsAuthenticated) { @User.Claims.First(x => x.Type.Contains("nameidentifier")).Value } else { @Html.ActionLink("Sign in With Apple でサインインする", "SignInWithApple") }以上で導入は一旦完了です。
ログアウト処理を書いていませんが、Sign in with Apple だからといって変わったことはないのでここでは割愛します。Sign in With Apple の動作確認
最初の動作確認と同様に Web アプリケーションをデバッグ実行します。
Sign in With Apple でサインインする のリンクをクリックします。
サインインすると元の画面に戻ります。
Apple から取得したユーザーの ID が表示されます。
これでアプリやゲームに Sign in With Apple を組み込めますね!
参考
- 投稿日:2020-03-29T18:45:05+09:00
C# コンストラクタ初期化子
コンストラクタ初期化子について
経緯
先日仕事中、visual studioの自動補完を使っている際、
コンストラクタで見慣れない書き方を発見したので備忘録として記す。実際のソース
以下に実際のソースを記す。
Bairitu.cspublic class Bairitu { string mbairituName = string.Empty; string temp = string.Empty; public Bairitu(string bairituName) { this.mbairituName = bairituName; } public Bairitu( string bairituName,string temp ) : this(bairituName) { this.temp = temp; } }解説
上記のBairituクラスでは
- string型を引数にとるコンストラクタ
- string型引数を二つとるコンストラクタ
上記二つのコンストラクタが記述されていることが確認できる。
ここで下記の方法でインスタンスを生成する
呼び出しvar test = new Bairitu("hoge", "fuge");その結果メンバ変数の結果は下記のようになる
- mbairituName == "hoge"
- string temp == "fuge"
動きは下記の流れとなる
- 引数二つのコンストラクタが呼び出される。
- 引数二つのコンストラクタに「: this(bairituName)」が記述されているので引数一つのコンストラクタが実行される。
- 「this.mbairituName = bairituName;」コンストラクタの中身が実行される。
- 3.を実行後「this.temp = temp;」引数二つのコンストラクタが実行される。
注意点
本記事の内容には必要な前提の説明や補足がない場合がありますのでご注意ください
間違いがあれば編集リクエストまでお願いします。
- 投稿日:2020-03-29T13:46:33+09:00
お手軽Linux Guiアプリ開発 入門
お手軽Linux Guiアプリ開発 入門
VisualStudioMacだけでWindows Formsを作る感覚でLinuxGUIアプリが作れます
利点
ターミナルで開発環境を整えなくてよい。VisualStudioMacが自動で整えてくれますツールボックスが表示される インターフェース Main Windowを選択
FiexedをMain Windowにドラッグします。編集可能領域を作成します
ボタンをドラッグします。ボタンを移動させるには白い四角をクリックしドラッグします
Clickedの文字の部分をダブルクリックします。クリックイベントメソッドが自動生成します。
クリックイベントメソッドが自動生成されます。ラベルに文字を入れる処理を加えます
ビルドされたプログラムはBinファイルの中に生成されています
ラズベリーパイで動作させるにはMonoDevelopをインストールします
感想
VisualStudioのWindowsFormを作成する感覚でお手軽に作成できました。
WindowsのVusualStudioだとGUIデザイナーが組み込まれていないため、GladeやSteticといったGUIデザイナーをダウンロードし、
交互に使わないといけない。クリックなどのイベントも自分で書かないといけない。Gladeをターミナルでダウンロードしたり、Monoの開発環境を手動で整えなくても、VisualStudioMacが自動で環境を作ってくれます。。
あとはWindows Formsを作る感覚でC#を書くだけで作れます。参考
C# VisualStudio for Mac でGtk#を使ってみた。gtk-sharp
次回 開発予定
C#からPythonを実行する 参考 C#からPythonスクリプトを呼び出す
Dapperの導入 参考 Dapperについてまとめてみた
Dapper Extensionの導入 参考 Dapperの拡張ライブラリ 比較検討
DataGrideViewとModelのバインディングの実現 参考URL treeView
Swaggerを使う Stoplight Studioなどを使う 参考 本当に使ってよかったOpenAPI (Swagger) ツール
RazurebyPiへのデプロイの仕方
Sqlite3の利用
- 投稿日:2020-03-29T13:08:57+09:00
userSettingsとapplicationSettings
(アプリケーション名).exe.Configの中身について
ユーザの場合、userSettings内に記載され、アプリケーションの場合は、applicationSettingsに記載される。
xxxx.exe.Configの中身<userSettings> <WindowsFormsApp1.Properties.Settings> <setting name="Setting1" serializeAs="String"> <value>aaa</value> </setting> </WindowsFormsApp1.Properties.Settings> </userSettings> <applicationSettings> <WindowsFormsApp1.Properties.Settings> <setting name="Setting2" serializeAs="String"> <value>bbb</value> </setting> </WindowsFormsApp1.Properties.Settings> </applicationSettings>userSettings
読み出しtextBox1.Text = Properties.Settings.Default.Setting1Save()で設定した値を保存する。
値の設定と保存// 値の設定 Properties.Settings.Default.Setting1 = "a" // 全部まとめて保存 Properties.Settings.Default.Save();デフォルト値に戻すProperties.Settings.Default.Reset();
- 保存先は、C:\Users(ユーザ名)\AppData\Local(アプリケーション名)\xxxxxx\user.config にある。
- デフォルト値は、(アプリケーション名).exe.Config
applicationSettings
- 読み出しの方法はuseSettingと同じだが、保存ができない。
(メモ) 1つの設定値に複数の値を設定する
カンマで区切って文字列として設定しておき、Splitで分離する。
xxxx.exe.Config<setting name="Setting1" serializeAs="String"> <value>aaa,bbb,ccc</value> </setting>comboBox1.Items.AddRange(Properties.Settings.Default.Setting1.Split(','));参考
- 投稿日:2020-03-29T00:57:13+09:00
C#からWordテンプレートに文字を挿入する方法
Word側の設定
- 挿入 > クイックパーツ > フィールド
- MergeFieldを選択
- フィールド名に適当な名前をつける(c#から参照する名前。例:CustomerName)
using Microsoft.Office.Interop.Word; // テンプレートのファイルパス private string templete = @"C:\tmp\templete.docx"; public void InsertWord(string field, string word) { object missing = Type.Missing; var app = new Application(); var doc = app.Documents.Open(templete, ref missing, true); foreach (Field myMergeField in doc.Fields) { Range rngFieldCode = myMergeField.Code; String fieldText = rngFieldCode.Text; if (fieldText.StartsWith(" MERGEFIELD")) { Int32 endMerge = fieldText.IndexOf("\\"); Int32 fieldNameLength = fieldText.Length - endMerge; string fieldName = fieldText.Substring(11, endMerge - 11); fieldName = fieldName.Trim(); if (fieldName == field) { myMergeField.Select(); app.Selection.TypeText(word); } } } // 保存パス doc.SaveAs(@"C:\tmp\output.docx"); var doc_close = (_Document)doc; doc_close.Close(); var applicationclose = (_Application)app; applicationclose.Quit(); }参考
- 投稿日:2020-03-29T00:20:54+09:00
LINQでStdev (標準偏差)
背景
LINQの標準ライブラリには標準偏差を求めるメソッドがない。
なので拡張メソッドを自作
詳しくはこちら
※ちなみに、ここで求めているのは母集団標準偏差(ExcelのStdev.P)ですコード
アルゴリズムはこちらを参照させていただきました。
自乗和を先に求めるのがポイント※注意
ジェネリック四則演算用クラス読込が必要
こちら参照くださいpublic static double Stdev<T>(this IEnumerable<T> src) { if(!src.Any()) throw new InvalidOperationException("Cannot compute median for an empty set."); //ジェネリックの四則演算用クラス var ao = new ArithmeticOperation<T>(); //四則演算できない型のとき、例外を投げる if (!ao.ArithmeticOperatable(typeof(T))) throw new InvalidOperationException("Cannot compute arithmetic operation"); //Doubleにキャストして処理を進める var doubleList = new List<double>(); foreach (var srcRow in src) doubleList.Add(Convert.ToDouble(srcRow)); //平均値算出 double mean = doubleList.Average(); //自乗和算出 double sum2 = doubleList.Select(a => a * a).Sum(); //分散 = 自乗和 / 要素数 - 平均値^2 double variance = sum2 / doubleList.Count - mean * mean; //標準偏差 = 分散の平方根 return Math.Sqrt(variance); }結果
List<int> iList = new List<int> { 1, 2, 3, 4, 5 }; List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 }; //標準偏差(int) Console.WriteLine(iList.Stdev().ToString()); //標準偏差(double) Console.WriteLine(dList.Stdev().ToString());1.4142135623731 0.28613807855649正常に標準偏差が出せていそうです
苦労したところ
・Generic → doubleへのキャスト
平均や二乗和を求めるためのAverage()やSum()がGeneric型では使えない。
doubleにキャストしようとしたが、ここに書いてあるobjectキャストを挟む方法ではエラー発生
ここに書いてるConvert.ToDouble()でキャストできた(foreachの中なので、キャスト部分の動作速度が若干不安ではありますが)
- 投稿日:2020-03-29T00:20:54+09:00
LinQでStdev (標準偏差)
背景
LinQの標準ライブラリには標準偏差を求めるメソッドがない。
なので拡張メソッドを自作
詳しくはこちら
※ちなみに、ここで求めているのは母集団標準偏差(ExcelのStdev.P)ですコード
アルゴリズムはこちらを参照させていただきました。
自乗和を先に求めるのがポイント※注意
ジェネリック四則演算用クラス読込が必要
こちら参照くださいpublic static double Stdev<Type>(this IEnumerable<Type> src) { if(!src.Any()) throw new InvalidOperationException("Cannot compute median for an empty set."); //ジェネリックの四則演算用クラス var ao = new ArithmeticOperation<Type>(); //四則演算できない型のとき、例外を投げる if (!ao.ArithmeticOperatable(typeof(Type))) throw new InvalidOperationException("Cannot compute arithmetic operation"); //Doubleにキャストして処理を進める var doubleList = new List<double>(); foreach (var srcRow in src) doubleList.Add(Convert.ToDouble(srcRow)); //平均値算出 double mean = doubleList.Average(); //自乗和算出 double sum2 = doubleList.Select(a => a * a).Sum(); //分散 = 自乗和 / 要素数 - 平均値^2 double variance = sum2 / doubleList.Count - mean * mean; //標準偏差 = 分散の平方根 return Math.Sqrt(variance); }結果
List<int> iList = new List<int> { 1, 2, 3, 4, 5 }; List<double> dList = new List<double> { 3.2, 3.5, 3.6, 4 }; //標準偏差(int) Console.WriteLine(iList.Stdev().ToString()); //標準偏差(double) Console.WriteLine(dList.Stdev().ToString());1.4142135623731 0.28613807855649正常に標準偏差が出せていそうです
苦労したところ
・Generic → doubleへのキャスト
平均や二乗和を求めるためのAverage()やSum()がGeneric型では使えない。
doubleにキャストしようとしたが、ここに書いてあるobjectキャストを挟む方法ではエラー発生
ここに書いてるConvert.ToDouble()でキャストできた(foreachの中なので、キャスト部分の動作速度が若干不安ではありますが)