20190227のC#に関する記事は11件です。

Rx風記法を用いてジェネリック内で演算するライブラリを作成しました

追記(3/3):
内部実装について少々記述。
関数と演算子の対応について少々情報を追加。
いくつかの関数に問題が発生したため、削除。
代わりの解決策を載せておきます。

ライブラリのダウンロード

https://gist.github.com/solitarywalker/74dd5d782f4a8d6b9005cdfcffa8b7fe
ライセンスはCC0ですので、好きに使ってください。

概要

C#のジェネリックの中では演算子を使うことができないため、式木かdynamicによる実装が必要でした。
dynamicによる実装は非常に単純になる一方で、パフォーマンスや型安全性の面から個人的にはあまり用いたくありませんでした。
そこで、自分が今までにやってきたことと、ネット上のさまざまな方の知識をうまく生かして、
ジェネリック内演算用のライブラリを作成しました。

他のサイトにあるコードと異なる点は、
・関数を演算子のように記述できる(Rx風記法)
というところです。

追記:
当初は異なる型同士でも演算ができる予定でしたが、
式木の仕様上(?)、上手く出来ないことがわかりました……。

使い方

基本的な使い方

int temp;

//以下の式はどれも9 / 3の結果をtempに代入します。
temp = Calc.DivResult(9, 3);
temp = Calc.Div(9, 3).result;
temp = Calc.Value(9).DivResult(3);
temp = Calc.Value(9).Div(3).result;

//以下の式はどれも(2 + 6 - 5) * 9の結果をtempに代入します。
temp = Calc.Add(2, 6).Sub(5).MulResult(9);
temp = Calc.Add(2, 6).Sub(5).Mul(9).result;
temp = Calc.Value(2).Add(6).Sub(5).MulResult(9);
temp = Calc.Value(2).Add(6).Sub(5).Mul(9).result;

Rx風メソッドチェーンで計算結果をつなぎ合わせていき、
最終的な結果はResultで終わる関数や変数で出すことで、
分かりやすくなるように心がけたつもりです。

パフォーマンスの面からすると、
最初をStart関数にするかそれ以外にするかの差は特にありませんが、
最後は変数で終えるより関数で終えたほうが効率が良いです。(微々たる差ではある)

可読性を邪魔しないように、
式木のメソッド名とは異なる名前で命名したメソッドがいくつかあります。
直感に合わないようであれば、別の名前に置き換えて利用してください。
関数と演算子の対応は以下の通りです。

関数 対応演算子 備考
Plus + 単項演算子版
Minus - 同上
Add + 二項演算子版
Sub - 同上
Mul *
Div /
Mod %

ライブラリ以外の演算子について

比較演算子(==・!=・<=・>=・<・>)は、CompareToメソッドを使ってください。
例として下に書いておきます。

//ライブラリと混ぜて使うならこんな感じです。
Calc.AddResult(10, 5).CompareTo(2);

//IComparableの制約をつけることによってジェネリックもできます。
bool equal<T>(T a, T b) where T : IComparable {
    if (a.CompareTo(b) == 0) return true;
    else return false;
}

キャストにはisやas演算子が使えます。

int? Cast<T>(T a) => a as int?;
bool IsCast<T>(T a) => a is int?;

速度比較

式木で作成したのメソッドはどのくらい早いのだろうか気になったため、実際に処理時間を計測することにしてみました。
比較に使ったパソコンがこちら。
SnapCrab_NoName_2019-3-2_13-37-45_No-00.png

計測用のコードの土台はこちら。

    public static void Main(string[] args) {

        int temp = default;
        var stopwatch = new Stopwatch();

        stopwatch.Start();

        //temp = ;  ここに書きたい式を書きます

        stopwatch.Stop();

        Console.WriteLine("Hogeの処理時間");
        Console.WriteLine(stopwatch.Elapsed);

    }

今回比較対象にする式はこれ。

temp = 2 + 5 - 1;

試しに、上の式の処理時間を計測してみると、

演算子の処理時間
00:00:00.0000019

となりました。やっぱり速い……;;

さて、まずはdynamicを用いた場合の時間を計測してみましょう。 つまり、

temp = (dynamic)2 + (dynamic)5 - (dynamic)1;

です。計測結果は、

Dynamicの処理時間
00:00:00.1708298

でした。
次にCallSiteクラス(dynamicの内部実装に使われているクラス)を用いたときの時間を計測してみましょう。

CallSite<Func<CallSite, object, int>> Cast = CallSite<Func<CallSite, object, int>>.Create(Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(int), null));
temp = Cast.Target(Cast, 2) + Cast.Target(Cast, 5) - Cast.Target(Cast, 1);

少々長いですが、動的コードを自前で書いているので仕方がありません……。
計測結果は……

CallSiteの処理時間
00:00:00.1486958

と少々早くなりました。
次に、今回のライブラリを用いた場合を見てみましょう。

temp = Calc.Add(2, 5).Sub(1).result;

計測結果は……

Calcの処理時間
00:00:00.0069823

となりました。20倍以上高速ですね!
また、より速い書き方である、

temp = Calc.Add(2, 5).SubResult(1);

も計測しました。

Calcの処理時間
00:00:00.0067120

微々たる差ではありますが、速いことが分かりました!

また、このCalcライブラリは2回目以降からより早く動きます!
テストコードはこちら。

public static void Main(string[] args) {

        int temp = default;
        var stopwatch = new Stopwatch();

        stopwatch.Start();

        temp = Calc.Add(2, 5).SubResult(1);

        stopwatch.Stop();

        Console.WriteLine("1回目の処理時間");
        Console.WriteLine(stopwatch.Elapsed);

        stopwatch.Restart();

        temp = Calc.Mul(3, 5).DivResult(9);

        stopwatch.Stop();

        Console.WriteLine("2回目の処理時間");
        Console.WriteLine(stopwatch.Elapsed);

    }

結果はこうなりました!

1回目の処理時間
00:00:00.0070866
2回目の処理時間
00:00:00.0008485

異なる同士での演算は不可能だと思われる

当初、式木の中で異なる型同士の演算ができると思いこんでいて、このようなコードを書いていました。

public class Calc<T1>
{
    public T1 result { get; }
    public Calc(T1 left) => result = left;
    public TR AddResult<T2, TR>(T2 right) => new BinaryOperator<T1, T2, TR>((p1, p2) => Expression.Add(p1, p2)).expression(result, right);
}

public static class Calc {
    public static Calc<TR> AddResult<T1, T2, TR>(T1 left, T2 right) => new Calc<T1>(left).AddResult<T2, TR>(right);
}

この状態で、次のコードを処理させてみました。

Calc.AddResult<double, int, double>(5.0, 2);

すると……

System.InvalidOperationException: バイナリ演算子 Add が型 'System.Double' と 'System.Int32' に対して定義されていません。

そもそも定義がないとエラーが来てしまいました。原因はよくわかりません(心折れました)。
現状では少なくとも、式木では異なる型での演算がそもそもできないことがある、としか言えません。

まとめ

ジェネリック内で演算をする場合はdynamicを使わずともこのライブラリを用いることである程度高速に動作させることができます。
ぜひ使ってみてください。

参考文献

https://qiita.com/solitary_walker/items/a3f83a5b562059a029f2
(自著)
https://qiita.com/Zuishin/items/61fc8807d027d5cea329
(@Zuishinさん著)
https://qiita.com/leetmikeal/items/1c4407d8e3dec39a3331
(@leetmekeaiさん著)
https://qiita.com/matarillo/items/25b837a419e2c82e1496
(@matarilloさん著)
https://ufcpp.net/study/csharp/sm_genericop.html
(未確認飛行 Cより)

編集履歴

2/27:
ファイルにusingの指定がなかったのを修正しました。
2/28:
速度比較してみました。
dynamicより異様なほどに早かったです。
わざわざ数値型に限定する意味があまりないことが分かったので、シンプルな実装へ書き換えました。
3/1:
編集が多くなってきたので編集履歴欄を一番下に設けました。
異なる引数でも演算ができるように機能を追加しました!
3/2:
記事の内容の整理をしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

演算子・Rx風記法を用いてジェネリック内で演算するライブラリを作成しました

追記(3/3):
演算子でも演算ができるように変更しました。
そのためタイトルも変更。
いろいろと大幅に変わりましたが、これ以上の更新はあったとしても小規模になると思います。

ライブラリのダウンロード

https://gist.github.com/solitarywalker/74dd5d782f4a8d6b9005cdfcffa8b7fe
ライセンスはCC0ですので、好きに使ってください。

概要

C#のジェネリックの中では演算子を使うことができないため、式木かdynamicによる実装が必要でした。
dynamicによる実装は非常に単純になる一方で、
パフォーマンスや型安全性の面から個人的にはあまり用いたくありませんでした。
そこで、自分が今までにやってきたことと、ネット上のさまざまな方の知識をうまく生かして、
ジェネリック内演算用のライブラリを作成しました。

他のサイトにあるコードと異なる点は、
・関数を演算子のように記述できる(Rx風記法)
というところです。

追記:
当初は異なる型同士でも演算ができる予定でしたが、
式木の仕様上(?)、上手く出来ないことがわかりました……。

使い方

基本的な使い方

int temp;

//以下の式はどれも9 / 3の結果をtempに代入します。
temp = Calc.DivResult(9, 3);
temp = Calc.Div(9, 3).result;
temp = Calc.Value(9).DivResult(3);
temp = Calc.Value(9).Div(3).result;
temp = (Calc.Value(9) / Calc.Value(3)).result;

//以下の式はどれも(2 + 6 - 5) * 9の結果をtempに代入します。
temp = Calc.Add(2, 6).Sub(5).MulResult(9);
temp = Calc.Add(2, 6).Sub(5).Mul(9).result;
temp = Calc.Value(2).Add(6).Sub(5).MulResult(9);
temp = Calc.Value(2).Add(6).Sub(5).Mul(9).result;
temp = temp = ((Calc.Value(2) + Calc.Value(6) - Calc.Value(5)) * Calc.Value(9)).result;

Rx風メソッドチェーンや演算子で計算結果をつなぎ合わせていき、
最終的な結果はResultで終わる関数や変数で出すことで、
分かりやすくなるように心がけたつもりです。

パフォーマンスの面では、

temp = Calc.DivResult(9, 3);
temp = Calc.Div(9, 3).result;
temp = Calc.Value(9).DivResult(3);
temp = Calc.Value(9).Div(3).result;
temp = (Calc.Value(9) / Calc.Value(3)).result;

のうち、早い順に書いていくと、
1番目=3番目>2番目=4番目>5番目
となります。

可読性を邪魔しないように、
式木のメソッド名とは異なる名前で命名したメソッドがいくつかあります。
直感に合わないようであれば、別の名前に置き換えて利用してください。
関数と演算子の対応は以下の通りです。

関数 対応演算子 備考
Plus + 単項演算子版
Minus - 同上
Add + 二項演算子版
Sub - 同上
Mul *
Div /
Mod %

ライブラリ以外の演算子について

比較演算子(==・!=・<=・>=・<・>)は、CompareToメソッドを使ってください。
例として下に書いておきます。

//ライブラリと混ぜて使うならこんな感じです。
Calc.AddResult(10, 5).CompareTo(2);

//IComparableの制約をつけることによってジェネリックもできます。
bool equal<T>(T a, T b) where T : IComparable {
    if (a.CompareTo(b) == 0) return true;
    else return false;
}

キャストにはisやas演算子が使えます。

int? Cast<T>(T a) => a as int?;
bool IsCast<T>(T a) => a is int?;

速度比較

式木で作成したのメソッドはどのくらい早いのだろうか気になったため、実際に処理時間を計測することにしてみました。
比較に使ったパソコンがこちら。
SnapCrab_NoName_2019-3-2_13-37-45_No-00.png

計測用のコードの土台はこちら。

    public static void Main(string[] args) {

        int temp = default;
        var stopwatch = new Stopwatch();

        stopwatch.Start();

        //temp = ;  ここに書きたい式を書きます

        stopwatch.Stop();

        Console.WriteLine("Hogeの処理時間");
        Console.WriteLine(stopwatch.Elapsed);

    }

今回比較対象にする式はこれ。

temp = 2 + 5 - 1;

試しに、上の式の処理時間を計測してみると、

演算子の処理時間
00:00:00.0000019

となりました。やっぱり速い……;;

さて、まずはdynamicを用いた場合の時間を計測してみましょう。 つまり、

temp = (dynamic)2 + (dynamic)5 - (dynamic)1;

です。計測結果は、

Dynamicの処理時間
00:00:00.1708298

でした。
次にCallSiteクラス(dynamicの内部実装に使われているクラス)を用いたときの時間を計測してみましょう。

CallSite<Func<CallSite, object, int>> Cast = CallSite<Func<CallSite, object, int>>.Create(Binder.Convert(CSharpBinderFlags.ConvertExplicit, typeof(int), null));
temp = Cast.Target(Cast, 2) + Cast.Target(Cast, 5) - Cast.Target(Cast, 1);

少々長いですが、動的コードを自前で書いているので仕方がありません……。
計測結果は……

CallSiteの処理時間
00:00:00.1486958

と少々早くなりました。
次に、今回のライブラリを用いた場合を見てみましょう。

temp = Calc.Add(2, 5).Sub(1).result;

計測結果は……

Calcの処理時間
00:00:00.0069823

となりました。20倍以上高速ですね!
また、より速い書き方である、

temp = Calc.Add(2, 5).SubResult(1);

も計測しました。

Calcの処理時間
00:00:00.0067120

微々たる差ではありますが、速いことが分かりました!

また、このCalcライブラリは2回目以降からより早く動きます!
テストコードはこちら。

public static void Main(string[] args) {

        int temp = default;
        var stopwatch = new Stopwatch();

        stopwatch.Start();

        temp = Calc.Add(2, 5).SubResult(1);

        stopwatch.Stop();

        Console.WriteLine("1回目の処理時間");
        Console.WriteLine(stopwatch.Elapsed);

        stopwatch.Restart();

        temp = Calc.Mul(3, 5).DivResult(9);

        stopwatch.Stop();

        Console.WriteLine("2回目の処理時間");
        Console.WriteLine(stopwatch.Elapsed);

    }

結果はこうなりました!

1回目の処理時間
00:00:00.0070866
2回目の処理時間
00:00:00.0008485

異なる同士での演算は不可能だと思われる

当初、式木の中で異なる型同士の演算ができると思いこんでいて、このようなコードを書いていました。

public class Calc<T1>
{
    public T1 result { get; }
    public Calc(T1 left) => result = left;
    public TR AddResult<T2, TR>(T2 right) => new BinaryOperator<T1, T2, TR>((p1, p2) => Expression.Add(p1, p2)).expression(result, right);
}

public static class Calc {
    public static Calc<TR> AddResult<T1, T2, TR>(T1 left, T2 right) => new Calc<T1>(left).AddResult<T2, TR>(right);
}

この状態で、次のコードを処理させてみました。

Calc.AddResult<double, int, double>(5.0, 2);

すると……

System.InvalidOperationException: バイナリ演算子 Add が型 'System.Double' と 'System.Int32' に対して定義されていません。

そもそも定義がないとエラーが来てしまいました。原因はよくわかりません(心折れました)。
現状では少なくとも、式木では異なる型での演算がそもそもできないことがある、としか言えません。

まとめ

ジェネリック内で演算をする場合は、
dynamicを使わずともこのライブラリを用いることである程度高速に動作させることができます。
ぜひ使ってみてください。

参考文献

https://qiita.com/solitary_walker/items/a3f83a5b562059a029f2
(自著)
https://qiita.com/Zuishin/items/61fc8807d027d5cea329
(@Zuishinさん著)
https://qiita.com/leetmikeal/items/1c4407d8e3dec39a3331
(@leetmekeaiさん著)
https://qiita.com/matarillo/items/25b837a419e2c82e1496
(@matarilloさん著)
https://ufcpp.net/study/csharp/sm_genericop.html
(未確認飛行 Cより)

編集履歴

2/27:
ファイルにusingの指定がなかったのを修正しました。
2/28:
速度比較してみました。
dynamicより異様なほどに早かったです。
わざわざ数値型に限定する意味があまりないことが分かったので、シンプルな実装へ書き換えました。
3/1:
編集が多くなってきたので編集履歴欄を一番下に設けました。
異なる引数でも演算ができるように機能を追加しました!(勘違いだったようです……)
3/2:
記事の内容の整理をしました。
3/3:
内部実装について少々記述。
関数と演算子の対応について少々情報を追加。
いくつかの関数に問題が発生したため、削除。
代わりの解決策を載せておきます。
演算子を用いた演算もできるようにしてみました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ConditionalAttributeのメソッドの引数に副作用のある式を書くとどうなるか

概要

最近C#に入門していて、ConditionalAttributeなるものの存在を知りました。
例えば、[Conditional("AAA")]という属性をメソッドに与えている場合において、シンボルのAAAが定義されていなければ、そのメソッドを呼び出している箇所がコンパイルされなくなるというものです(メソッド自体はコンパイルされます)。

Program.cs
using System;
using System.Diagnostics;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // シンボルAAAが定義されていないため、下のHello()の呼び出しはコンパイルされない
            Hello();
            Console.WriteLine("Main()");
        }

        [Conditional("AAA")]
        static void Hello()
        {
            Console.WriteLine("Hello()");
        }
    }
}

実行結果

Main()

シンボルのAAAが定義されていないため、Hello()メソッドが呼び出されてないことが確認できます。

そこで、1つ疑問が浮かびました。
ConditionalAttributeのメソッドの引数に副作用のある式を書いたらどうなるのでしょうか。これは、C/C++のassert()において、意図せず副作用のある式を書いてしまい、挙動がDebugとReleaseのときで異なりバグが発生することがよくある?ことであり気になったからです。

例えば、C++でassert()に副作用のある式を書いてしまったソースコードは下記です。

main.cpp
#include <iostream>
#include <cassert>

using namespace std;

int main()
{
    int a = 10;

    assert(++a);

    cout << a << endl;

    return 0;
}

NDEBUGを定義するかしないかで、aの値が変化してしまいます。

$ clang++ main.cpp && ./a.out
11
$ clang++ -DNDEBUG main.cpp && ./a.out
10

検証

検証用のソースコードは下記です。

Program.cs
using System;
using System.Diagnostics;

namespace ConsoleApplication
{
    class Program
    {
        static int a = 10;

        static void Main(string[] args)
        {
            Hello(++a);

            Console.WriteLine("Main():{0}", a);
        }

        [Conditional("AAA")]
        static void Hello(int x)
        {
            Console.WriteLine("Hello():{0}", x);
        }
    }
}

実行結果

Main():10

というわけで、Hello(++a);がコンパイルされなくなっているので、++aは実行されず、aが11になることはありませんでした。
結論としては、ConditionalAttributeのメソッドの引数に副作用のある式を書くのは、あまり良いことでは無いかもしれません。

参考文献

ConditionalAttribute Class (System.Diagnostics) | Microsoft Docs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動画を自動トレース⇒Unityでキャラクターに踊ってもらう

やりたいこと

動画からモーションを自動トレースして、Unityで好きなキャラクターに踊って(動いて)もらう

OpenPoseや3d-pose-baselineなどディープラーニング技術を組み合わせて、動画からMMDモーションを自動生成するプログラムを@miu200521358様が開発しています。ここではMMDの代わりに、Unityで動かします。

MMDモーショントレース自動化への挑戦【ver1.00】(ニコニコ動画)
https://www.nicovideo.jp/watch/sm34626229

実行例)
mjua9-enr2z.gif

自動トレース元: https://nico.ms/sm27620009?from=20 :やっこ様、まりやん様
3Dモデル:Masscat、Unity-Chan(© Unity Technologies Japan/UCL)

どうやって実現するか

MMDモーション自動生成の手順の途中で生成される3Dポーズデータ(pos.txt)を利用します。
@romaroma様が実現方法を以下の記事で紹介しています。ただ、なぜか私の環境(モデル:Unity-Chan, MassCat、Unity:ver.2017.4.19f1)で上手く動かなかったため、@romaroma様の記事を参考にてプログラムを作成しました。

参考ページ @romaroma様
https://qiita.com/romaroma/items/ffbdae4ecfc4c8ff31cd

1.3Dポーズデータの作成

以下の@miu200521358様の記事の参考にして動画から3Dポーズデータ(pos.txt)を作成してください。
構築・実行には時間と手間がかかります。最初は1人の動画で試してみることをお勧めします。

【目次】MMDモーショントレース自動化への挑戦
https://qiita.com/miu200521358/items/d826e9d70853728abc51

  1. Windows への OpenPose導入手順
  2. Windows への FCRN-DepthPrediction-vmd の導入手順 【2019/2/15追加】
  3. Windows への 3d-pose-baseline-vmd の導入手順

※3.の手順でpos.txtが生成されるため、4.以降の手順は不要です。

2.キャラクターを動かす

上記の3で作成されるpos.txtと、下記のプログラム(Pos_txt_Reader.cs)を適当なフォルダに配置し、Pos_txt_Reader.csをキャラクターにアタッチ後、Pos_filenameにpos.txtのパスを指定し、再生してください。

プログラムはgithubにもあげておきます。スムージング処理や足IK処理をしていません。時間ができたら対応したいと思います。
https://github.com/kenkra/Unity-3d-pose-baseline

unitychan.png

Pos_txt_Reader.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;

// pos.txtのデータ
// https://github.com/miu200521358/3d-pose-baseline-vmd/blob/master/doc/Output.md
// 0 :Hip
// 1 :RHip
// 2 :RKnee
// 3 :RFoot
// 4 :LHip
// 5 :LKnee
// 6 :LFoot
// 7 :Spine
// 8 :Thorax
// 9 :Neck/Nose
// 10:Head
// 11:LShoulder
// 12:LElbow
// 13:LWrist
// 14:RShoulder
// 15:RElbow
// 16:RWrist

public class Pos_txt_Reader : MonoBehaviour
{
    float scale_ratio = 0.001f;  // pos.txtとUnityモデルのスケール比率
                                 // pos.txtの単位はmmでUnityはmのため、0.001に近い値を指定。モデルの大きさによって調整する
    float heal_position = 0.05f; // 足の沈みの補正値(単位:m)。プラス値で体全体が上へ移動する
    float head_angle = 15f; // 顔の向きの調整 顔を15度上げる

    public String pos_filename; // pos.txtのファイル名
    public Boolean debug_cube; // デバッグ用Cubeの表示フラグ
    public int start_frame; // 開始フレーム
    public String end_frame; // 終了フレーム  
    float play_time; // 再生時間 
    Transform[] bone_t; // モデルのボーンのTransform
    Transform[] cube_t; // デバック表示用のCubeのTransform
    Vector3 init_position; // 初期のセンターの位置
    Quaternion[] init_rot; // 初期の回転値
    Quaternion[] init_inv; // 初期のボーンの方向から計算されるクオータニオンのInverse
    List<Vector3[]> pos; // pos.txtのデータを保持するコンテナ
    int[] bones = new int[10] { 1, 2, 4, 5, 7, 8, 11, 12, 14, 15 }; // 親ボーン
    int[] child_bones = new int[10] { 2, 3, 5, 6, 8, 10, 12, 13, 15, 16 }; // bonesに対応する子ボーン
    int bone_num = 17;
    Animator anim;
    int s_frame;
    int e_frame;

    // pos.txtのデータを読み込み、リストで返す
    List<Vector3[]> ReadPosData(string filename) {
        List<Vector3[]> data = new List<Vector3[]>();

        List<string> lines = new List<string>();
        StreamReader sr = new StreamReader(filename);
        while (!sr.EndOfStream) {
            lines.Add(sr.ReadLine());
        }
        sr.Close();

        foreach (string line in lines) {
            string line2 = line.Replace(",", "");
            string[] str = line2.Split(new string[] { " " }, System.StringSplitOptions.RemoveEmptyEntries); // スペースで分割し、空の文字列は削除

            Vector3[] vs = new Vector3[bone_num];
            for (int i = 0; i < str.Length; i += 4) {
                vs[(int)(i/4)] = new Vector3(-float.Parse(str[i + 1]), float.Parse(str[i + 3]), -float.Parse(str[i + 2]));
            }
            data.Add(vs);
        }
        return data;
    }

    // BoneTransformの取得。回転の初期値を取得
    void GetInitInfo()
    {
        bone_t = new Transform[bone_num];
        init_rot = new Quaternion[bone_num];
        init_inv = new Quaternion[bone_num];

        bone_t[0] = anim.GetBoneTransform(HumanBodyBones.Hips);
        bone_t[1] = anim.GetBoneTransform(HumanBodyBones.RightUpperLeg);
        bone_t[2] = anim.GetBoneTransform(HumanBodyBones.RightLowerLeg);
        bone_t[3] = anim.GetBoneTransform(HumanBodyBones.RightFoot);
        bone_t[4] = anim.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
        bone_t[5] = anim.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
        bone_t[6] = anim.GetBoneTransform(HumanBodyBones.LeftFoot);
        bone_t[7] = anim.GetBoneTransform(HumanBodyBones.Spine);
        bone_t[8] = anim.GetBoneTransform(HumanBodyBones.Neck);
        bone_t[10] = anim.GetBoneTransform(HumanBodyBones.Head);
        bone_t[11] = anim.GetBoneTransform(HumanBodyBones.LeftUpperArm);
        bone_t[12] = anim.GetBoneTransform(HumanBodyBones.LeftLowerArm);
        bone_t[13] = anim.GetBoneTransform(HumanBodyBones.LeftHand);
        bone_t[14] = anim.GetBoneTransform(HumanBodyBones.RightUpperArm);
        bone_t[15] = anim.GetBoneTransform(HumanBodyBones.RightLowerArm);
        bone_t[16] = anim.GetBoneTransform(HumanBodyBones.RightHand);

        // Spine,LHip,RHipで三角形を作ってそれを前方向とする。
        Vector3 init_forward = TriangleNormal(bone_t[7].position, bone_t[4].position, bone_t[1].position);
        init_inv[0] = Quaternion.Inverse(Quaternion.LookRotation(init_forward));

        init_position = bone_t[0].position;
        init_rot[0] = bone_t[0].rotation;
        for (int i = 0; i < bones.Length; i++) {
            int b = bones[i];
            int cb = child_bones[i];

            // 対象モデルの回転の初期値
            init_rot[b] = bone_t[b].rotation;
            // 初期のボーンの方向から計算されるクオータニオン
            init_inv[b] = Quaternion.Inverse(Quaternion.LookRotation(bone_t[b].position - bone_t[cb].position,init_forward));
        }
    }

    // 指定の3点でできる三角形に直交する長さ1のベクトルを返す
    Vector3 TriangleNormal(Vector3 a, Vector3 b, Vector3 c)
    {
        Vector3 d1 = a - b;
        Vector3 d2 = a - c;

        Vector3 dd = Vector3.Cross(d1, d2);
        dd.Normalize();

        return dd;
    }

    // デバック用cubeを生成する。生成済みの場合は位置を更新する
    void UpdateCube(int frame)
    {
        if (cube_t == null) {
            // 初期化して、cubeを生成する
            cube_t = new Transform[bone_num];

            for (int i = 0; i < bone_num; i++) {
                Transform t = GameObject.CreatePrimitive(PrimitiveType.Cube).transform;
                t.transform.parent = this.transform;
                t.localPosition = pos[frame][i] * scale_ratio;
                t.name = i.ToString();
                t.localScale = new Vector3(0.05f, 0.05f, 0.05f);
                cube_t[i] = t;

                Destroy(t.GetComponent<BoxCollider>());
            }
        }
        else {
            // モデルと重ならないように少しずらして表示
            Vector3 offset = new Vector3(1.2f, 0, 0);

            // 初期化済みの場合は、cubeの位置を更新する
            for (int i = 0; i < bone_num; i++) {
                cube_t[i].localPosition = pos[frame][i] * scale_ratio + new Vector3(0, heal_position, 0) + offset;
            }
        }
    }

    void Start()
    {
        anim = GetComponent<Animator>();
        play_time = 0;
        if (System.IO.File.Exists (pos_filename) == false) {
            Debug.Log("<color=blue>Error! Pos file not found(" + pos_filename + "). Check Pos_filename in Inspector.</color>");
        }
        pos = ReadPosData(pos_filename);
        GetInitInfo();
        if (pos != null) {
            // inspectorで指定した開始フレーム、終了フレーム番号をセット
            if (start_frame >= 0 && start_frame < pos.Count) {
                s_frame = start_frame;
            } else {
                s_frame = 0;
            }
            int ef;
            if (int.TryParse(end_frame, out ef)) {
                if (ef >= s_frame && ef < pos.Count) {
                    e_frame = ef;
                } else {
                    e_frame = pos.Count - 1;
                }
            } else {
                e_frame = pos.Count - 1;
            }
            Debug.Log("End Frame:" + e_frame.ToString());
        }
    }

    void Update()
    {
        if (pos == null) {
            return;
        }
        play_time += Time.deltaTime;

        int frame = s_frame + (int)(play_time * 30.0f);  // pos.txtは30fpsを想定
        if (frame > e_frame) {
            play_time = 0;  // 繰り返す
            frame = s_frame;
        }

        if (debug_cube) {
            UpdateCube(frame); // デバッグ用Cubeを表示する
        }

        Vector3[] now_pos = pos[frame];

        // センターの移動と回転
        Vector3 pos_forward = TriangleNormal(now_pos[7], now_pos[4], now_pos[1]);
        bone_t[0].position = now_pos[0] * scale_ratio + new Vector3(init_position.x, heal_position, init_position.z);
        bone_t[0].rotation = Quaternion.LookRotation(pos_forward) * init_inv[0] * init_rot[0];

        // 各ボーンの回転
        for (int i = 0; i < bones.Length; i++) {
            int b = bones[i];
            int cb = child_bones[i];
            bone_t[b].rotation = Quaternion.LookRotation(now_pos[b] - now_pos[cb], pos_forward) * init_inv[b] * init_rot[b];
        }

        // 顔の向きを上げる調整。両肩を結ぶ線を軸として回転
        bone_t[8].rotation = Quaternion.AngleAxis(head_angle, bone_t[11].position - bone_t[14].position) * bone_t[8].rotation;
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ASP.net】リピーター内のコントロールにアクセスする【C#】

リピーター内のコントロールにアクセスする

リピーター内のコントロールにアクセスしたい場合が多々あります。
例えば、リピーターで3回目の繰り返し時のデータの値を取得したいときなどです。

sample.aspx
<asp:Repeater ID="rpt_sample" runat="server" OnItemDataBound="rptsample_ItemDataBound">
    <ItemTemplate>
            <label runat="server" id="sample_name"><%# Eval("sample_name").ToString()%></label>
            <label runat="server" id="sample_no"><%# Eval("sample_no").ToString()%></label>
    </ItemTemplate>
</asp:Repeater>

こんな感じのシンプルなリピーターの場合は、簡単です。
foreachを使用します。

sample.aspx.cs
foreach (RepeaterItem item in rpt_sample.Items)
{
    HtmlGenericControl no = (HtmlGenericControl)item.FindControl("sample_no");
    HtmlGenericControl name = (HtmlGenericControl)item.FindControl("sample_name");
}

これで、各繰り返しのコントロールにアクセスできます。
forの繰り返し回数を判定すれば、すきな繰り返し回数の場所のコントロールを取得できます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MSDN語パーフェクト文法マスター

MSDN語とは

こんなやつ
いわゆる MSDN (と呼ばれていた) における日本語の言い回しのこと。

msdnlang-1.png

マスターすると何が嬉しい?

C# でドキュメント コメントを書く際に、標準ライブラリっぽくなります。
コメントのスタイルまで統一できるなんて、さすが C# は違う。

注意事項

2010 年代前半にまとめた文章を発掘して、そのままリリースしています。
すでに内容が古いかもしれません!

MSDN語

文体

文体は「です・ます」調で記述します。

-/// これは悪いコメントだ。
+/// これは良いコメントです。

param タグや returns タグの最初の文は名詞句で記述します。

-/// <param name="value">悪い値です。</param>
+/// <param name="value">良い値。</param>

句読点は「、」および「。」を使用します。

-/// これは, 悪いコメントです.
+/// これは、良いコメントです。

あらゆる文章の最後には「。」を記述します。

-/// これは悪いコメントです
+/// これは良いコメントです。

括弧は半角 ( ) を使用します。

-/// これは(悪い)コメントです。
+/// これは (良い) コメントです。

アルファベットや数字は半角文字を使用します。

-/// これは1番悪いコメントです。
+/// これは 1 番良いコメントです。

空白

全角文字と半角文字の境界には半角スペース を挿入します。

-/// <param name="source">これは悪いC#のコメント。</param>
+/// <param name="source">これは良い C# のコメント。</param>

句読点の前後に半角文字が続く場合は半角スペース を挿入しません。

-/// <param name="source">これは、 C# の悪いコメント。</param>
+/// <param name="source">これは、C# の良いコメント。</param>

括弧の直中に全角文字が続く場合は半角スペース を挿入しません。

-/// <param name="source">悪いコメント ( テスト )。</param>
+/// <param name="source">良いコメント (テスト)。</param>

タグの前後に全角文字が続く場合は半角スペース を挿入しません。

-/// <param name="source"> 悪いコメント。 </param>
+/// <param name="source">良いコメント。</param>

語句

(迷った時に) 文の最後は 表します。表現します。識別します。定義します。提供します。公開します。 などで終わります。

コンストラクタの summary セクションは {型名} クラスの新しいインスタンスを初期化します。 とします。
コンストラクタがパラメータを持つ場合や、コンストラクタがオーバーロードされている場合は {型名} クラスの新しいインスタンスを、〜初期化します。 とします。

public sealed class Foo
{
    /// <summary>
    /// <see cref="Foo"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    public Foo() { ... }

    /// <summary>
    /// <see cref="Foo"/> クラスの新しいインスタンスを、指定した整数値で初期化します。
    /// </summary>
    public Foo(int n) { ... }
}

静的コンストラクタの summary セクションは {型名} クラスを初期化します。 とします。

public static class Foo
{
    /// <summary>
    /// <see cref="Foo"/> クラスを初期化します。
    /// </summary>
    static Foo() { ... }
}

イベントの summary セクションは ~に発生します。~ときに発生します。 とします。

/// <summary>
/// プロパティ値が変更されたときに発生します。
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

(迷った時に) イベント ハンドラ メソッドの param セクションは イベント ソース。 および イベント データ。 とします。

/// <summary>
/// ...
/// </summary>
/// <param name="sender">イベント ソース。</param>
/// <param name="e">イベント データ。</param>
private void button_Click(object sender, EventArgs e);

プロパティの summary セクションは ~を取得または設定します。 とします。

/// <summary>
/// ビューからの応答を取得または設定します。
/// </summary>
/// <value>ビューからの応答。</value>
object Response { get; set; }

読み取り専用プロパティの summary セクションは ~を取得します。 とします。

/// <summary>
/// このインタラクション メッセージを一意に識別できるグローバル一意識別子を取得します。
/// </summary>
/// <value>このインタラクション メッセージを一意に識別できるグローバル一意識別子。</value>
Guid Id { get; }

bool を返すプロパティの summary セクションには ~かどうかを示す値 を含めます。

/// <summary>
/// このオブジェクトが有効かどうかを示す値を取得します。
/// </summary>
/// <value>このオブジェクトが有効な場合は <c>true</c>。それ以外の場合は <c>false</c>。</value>
bool IsEnabled { get; }

プロパティの既定値が重要な場合は summary セクションに 既定値は、~です。 と記述します。

/// <summary>
/// このビュー モデルで使用できるインタラクション メッセンジャーを取得または設定します。
/// </summary>
/// <value>
/// このビュー モデルで使用できるインタラクション メッセンジャー。
/// 既定値は、<see cref="InteractionMessenger.Default"/> です。
/// </value>
IInteractionMessenger Messenger { get; set; }

bool を返すメソッドの summary セクションは ~かどうかを判断します。 とします。

/// <summary>
/// 現在の状態でこのコマンドを実行できるかどうかを判断します。
/// </summary>
/// <param name="parameter">コマンドで使用されたデータ。</param>
/// <value>このコマンドを実行できる場合は <c>true</c>。それ以外の場合は <c>false</c>。</value>
bool CanExecute(T parameter);

bool を返すプロパティまたはメソッドの value セクションは ~の場合は <c>true</c>。それ以外の場合は <c>false</c>。 とします。

/// <summary>
/// このオブジェクトが有効かどうかを示す値を取得します。
/// </summary>
/// <value>このオブジェクトが有効な場合は <c>true</c>。それ以外の場合は <c>false</c>。</value>
bool IsEnabled { get; }

イベントを発生させるメソッドの summary セクションは {イベント} イベントを発生させます。 とします。

/// <summary>
/// <see cref="ICommand.CanExecuteChanged"/> イベントを発生させます。
/// </summary>
void RaiseCanExecuteChanged();

メソッドが派生クラスによるイベントの購読を目的としている場合は summary セクションを {イベント} イベントが発生したときに呼びだされます。 とします。

/// <summary>
/// <see cref="Attached"/> イベントが発生したときに呼びだされます。
/// </summary>
protected virtual void OnAttached() { ... }

ArgumentNullException のドキュメントは <paramref name="{パラメータ}"/> が <c>null</c> です。 とします。

/// <summary>
/// <see cref="AnonymousDisposable"/> クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="dispose"><see cref="IDisposable.Dispose"/> から一度だけ呼びだされるデリゲート。</param>
/// <exception cref="ArgumentNullException"><paramref name="dispose"/> が <c>null</c> です。</exception>
public AnonymousDisposable([NotNull] Action dispose)
{
    _dispose = dispose ?? throw new ArgumentNullException(nameof(dispose));
}

ObjectDisposedException のドキュメントは オブジェクトは解放済みです。 とします。

/// <exception cref="ObjectDisposedException">オブジェクトは解放済みです。</exception>

宣言が abstract の場合は summary セクションの先頭に 派生クラスで実装されると、 と記述します。

/// <summary>
/// 派生クラスで実装されると、このトリガー アクション固有のアクションを実行します。
/// </summary>
/// <param name="parameter">パラメータ。</param>
protected abstract void Invoke(object parameter);

型パラメータが共変または反変の場合は決められた内容を記述します。

/// <summary>
/// </summary>
/// <typeparam name="TValue">
/// ...
/// このパラメータが反変の型パラメータです。
/// つまり、その指定した型を使用するか、それよりも弱い任意の派生型を使用することができます。
/// 共変性と反変性の詳細については、「ジェネリックの共変性と反変性」を参照してください。
/// </typeparam>
/// <typeparam name="TResult">
/// ...
/// このパラメータが共変の型パラメータです。
/// つまり、その指定した型を使用するか、それよりも強い任意の派生型を使用することができます。
/// 共変性と反変性の詳細については、「ジェネリックの共変性と反変性」を参照してください。
/// </typeparam>
public interface IObserver<in TValue, out TResult> { ... }

メソッドをシールし派生クラスに別のカスタマイズ ポイントを提供する場合は決められた内容を記述します。

/// <summary>
/// アクションを呼び出します。
/// </summary>
/// <param name="parameter">アクションへのパラメータ。</param>
/// <remarks>
/// このメソッドはシールされています。
/// このロジックを特別にオーバーライドするには、派生クラスで
/// <see cref="InvokeCore"/> をオーバーライドする必要があります。
/// </remarks>
protected override sealed void Invoke(object parameter)
{
    :
    InvokeCore(message);
}

/// <summary>
/// 派生クラスでオーバーライドされると、インタラクション メッセージを処理します。
/// </summary>
/// <param name="message">受信したインタラクション メッセージ。</param>
protected virtual void InvokeCore(object message) { ... }

依存関係プロパティのドキュメントには決められた内容を記述します。

#region IsFloating 依存関係プロパティ
/// <summary>
/// <see cref="IsFloating"/> 依存関係プロパティを識別します。
/// </summary>
[NotNull]
private static readonly DependencyPropertyKey IsFloatingPropertyKey
    = DependencyProperty.RegisterReadOnly(
        nameof(IsFloating),
        typeof(bool),
        typeof(FloatingLabel),
        new PropertyMetadata(false, IsFloatingChangedCallback));

/// <summary>
/// <see cref="IsFloating"/> 依存関係プロパティを識別します。
/// </summary>
[NotNull]
public static readonly DependencyProperty IsFloatingProperty = IsFloatingPropertyKey.DependencyProperty;

/// <summary>
/// フローティング ラベルがフロート表示されているかどうかを示す値を取得します。
/// これは依存関係プロパティです。
/// </summary>
/// <value>フローティング ラベルがフロート表示されている場合は <c>true</c>。それ以外の場合は <c>false</c>。</value>
public bool IsFloating
{
    get => (bool)GetValue(IsFloatingProperty);
    private set => SetValue(IsFloatingPropertyKey, value);
}

/// <summary>
/// <see cref="IsFloating"/> 依存関係プロパティが変更されたときに呼び出されます。
/// </summary>
/// <param name="d">イベント ソース。</param>
/// <param name="e">イベント データ。</param>
private static void IsFloatingChangedCallback([NotNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
    => ((FloatingLabel)d).OnIsFloatingChanged((bool)e.OldValue, (bool)e.NewValue);

/// <summary>
/// <see cref="IsFloating"/> 依存関係プロパティが変更されたときに呼び出されます。
/// </summary>
/// <param name="oldIsFloating">変更される前の値。</param>
/// <param name="newIsFloating">変更された後の値。</param>
protected virtual void OnIsFloatingChanged(bool oldIsFloating, bool newIsFloating) { ... }
#endregion

パラメータが out パラメータの場合は最後に このパラメータは初期化せずに渡されます。 と記述します。

/// <summary>
/// パケットを受信します。
/// このメソッドは、複数のスレッドから同時にアクセスすることができます。
/// </summary>
/// <param name="description">
/// 受信したパケットの概要。
/// このパラメータは初期化せずに渡されます。
/// </param>
/// <returns>パケットを受信しており、<paramref name="description"/> に値が格納された場合は <c>true</c>。それ以外の場合は <c>false</c>。</returns>
public override bool TryReceive(out PacketDescription description) { ... }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MagicOnionで独自暗号化処理を追加する

はじめに

MagicOnion についてはこちらをご覧ください。
今回は MagicOnion で独自暗号化処理を追加してみようと思います。独自暗号を挟む用途としては、ソーシャルゲームなどにおいてユーザー本人によるチート行為への対策などを想定しています。

gRPC Interceptor

MagicOnion において、暗号化のような MessagePack の byte 列に変換した後にすべき処理は gRPC の Interceptor という仕組みを使うことで実現できます。
Interceptor を使うとそれぞれの RPC の直前・直後に処理を挟むことができるようになります。
これは gRPC の機能なので、以下の内容は MagicOnion のみに限らず、素の gRPC でも使える内容になります。

実装

自作の Interceptor は Grpc.Core.Interceptors.Interceptor クラスを継承します。
サーバ側・クライアント側それぞれについて、RPC 種類ごとにメソッドが用意されているので、必要なものだけを override して実装することになります。

  • サーバ側
    • UnaryServerHandler
    • ServerStreamingServerHandler
    • ClientStreamingServerHandler
    • DuplexStreamingServerHandler
  • クライアント側
    • BlockingUnaryCall
    • AsyncUnaryCall
    • AsyncServerStreamingCall
    • AsyncClientStreamingCall
    • AsyncDuplexStreamingCall

以下では Unary の書き方のみ記載します。

サーバ側

以下のようにリクエスト、レスポンスそれぞれに処理を挟むことができます。

public class HogeInterceptor : Interceptor
{
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation
    ) {
        /* ここで request を加工する */

        // 通常処理
        var response = await base.UnaryServerHandler(request, context, continuation);

        /* ここで response を加工する */

        return response;
    }
}

クライアント側

クライアント側は戻り値が Task ではないので、レスポンスに処理を挟むのが若干面倒になっています。
一旦 AsyncUnaryCall を受け取ってから、新しい AsyncUnaryCall に諸々をコピーして返す必要があります。

public class HogeInterceptor : Interceptor
{
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation
    )
    {
        /* ここで request を加工する */

        var call = base.AsyncUnaryCall(request, context, continuation);

        var responseTask = call.ResponseAsync.ContinueWith(x => 
        {
            var response = x.Result;

            /* ここで response を加工する */

            return response;
        });

        return new AsyncUnaryCall<TResponse>(
            responseTask,
            call.ResponseHeadersAsync,
            call.GetStatus,
            call.GetTrailers,
            call.Dispose
        );
    }
}

適用方法

サーバ側

gRPC サーバの起動時、 Services に Interceptor を挟んだ ServerServiceDefinition を渡すことで機能するようになります。

var service = MagicOnionEngine.BuildServerServiceDefinition();
var server = new Grpc.Core.Server
{
    // ここで Interceptor を挟んだ ServerServiceDefinition を渡す
    Services = { service.ServerServiceDefinition.Intercept(new HogeInterceptor()) },
    // 挟まない場合はこう
    // Services = { service },
    Ports = { new ServerPort("localhost", 12345, ServerCredentials.Insecure) }
};
server.Start();

クライアント側

MagicOnionClient.Create の際に Channel そのままではなく Interceptor を挟んだ CallInvoker を渡すことで機能するようになります。

var channel = new Channel("localhost", 12345, ChannelCredentials.Insecure);
// ここで Interceptor を挟んだ CallInvoker を生成
var invoker = channel.Intercept(new HogeInterceptor());
var client = MagicOnionClient.Create<IMyFirstService>(invoker);
// 挟まない場合はこう
// var client = MagicOnionClient.Create<IMyFirstService>(channel);

暗号化

ようやく本題の暗号化ですが、上記のとおりに実装するだけのでそのままコードを載せます。
※暗号アルゴリズムについてはどうでもいいので省略します。

using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;

public class CipherInterceptor : Interceptor
{
    public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
        TRequest request,
        ServerCallContext context,
        UnaryServerMethod<TRequest, TResponse> continuation
    ) => Encrypt(await base.UnaryServerHandler(Decrypt(request), context, continuation));

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation
    ) {
        var call = base.AsyncUnaryCall(Encrypt(request), context, continuation);
        return new AsyncUnaryCall<TResponse>(
            call.ResponseAsync.ContinueWith(x => Decrypt(x.Result)),
            call.ResponseHeadersAsync,
            call.GetStatus,
            call.GetTrailers,
            call.Dispose
        );
    }

    private T Encrypt<T>(T data)
    {
        /* 略 */
    }

    private T Decrypt<T>(T data)
    {
        /* 略 */
    }
}

このクラスをサーバ・クライアントで共有することで、同じ暗号アルゴリズムを使っていることが簡単に保証できていい感じです。

まとめ

MagicOnion でも gRPC の Interceptor を使うことができます。今回はそれを用いて独自の暗号化処理を挟みました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#】wavファイルを鳴らす

表題のまんまなのでいきなり本文

using System.Media;

namespace app
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            string path = @"D:\exe\visualstudiocommunity\hogefuga\music.wav";
            SountPlayer music = new SoundPlayer(path);
            music.Play();
        }
    }
}

visual studio communityでF5を押下してビルドすると音楽が鳴ります。
勿論pathの部分は自分の環境にあったものを入れてください。

前回unityでゲーム作ろう!って記事を書いたのですが、そもそもC#なんぞやという状態だったので、まず素のC#で何か作ってみようと思いました。。
物理演算とかしないなら普通に作れますよね。まあunity使う気なら最初からunityで作ってもいいと思うのですが、覚えることが多そうなので先にプログラムだけ学ぼうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Project Euler 47,48,49問目

はじめに

C#を用いてProjectEulerに挑戦している人の記録です。VisualStudio2017で、1つのフォームアプリの中に問題番号のボタンを作成し、そのボタンをクリックすると結果をラベルに表示するという形式でやっています。

47問目

それぞれ2つの異なる素因数を持つ連続する2つの数が最初に現れるのは:

14 = 2 × 7
15 = 3 × 5

それぞれ3つの異なる素因数を持つ連続する3つの数が最初に現れるのは:

644 = 22 × 7 × 23
645 = 3 × 5 × 43
646 = 2 × 17 × 19

最初に現れるそれぞれ4つの異なる素因数を持つ連続する4つの数を求めよ. その最初の数はいくつか?

private void button47_Click(object sender, EventArgs e)
        {
            var PrimeList = new List<int>(Eratosthenes(1000000));
            int j = 0;
            var Hash = new HashSet<int>();
            int Count = 0;
            int i = 647;
            while (true)
            {
                int Number = i;
                while (Number > PrimeList[j])
                {
                    if (Number % PrimeList[j] == 0)
                    {
                        Number /= PrimeList[j];
                        Hash.Add(PrimeList[j]);
                    }
                    else { j++; }
                }
                Hash.Add(Number);
                if (Hash.Count == 4)
                {
                    Count++;
                }
                else
                {
                    Count = 0;
                }
                if (Count == 4)
                {
                    label1.Text = "Answer = " + (i - 3);
                    break;
                }
                Hash.Clear();
                Hash.TrimExcess();
                i++;
                j = 0;
            }
        }

素数のリストを使って、647から1ずつ足していって割り切れた数が重複せず4つあるかを見て、それが4つ連続している数字を答えとしました。

48問目

11 + 22 + 33 + ... + 1010 = 10405071317 である.

では, $1^1 + 2^2 + 3^3 + ... + 1000^{1000}$ の最後の10桁を求めよ.

private void button48_Click(object sender, EventArgs e)
        {
            BigInteger Number = 1;
            BigInteger Sum = 0;
            for (BigInteger i = 1; i <= 1000; i++)
            {
                BigInteger j = i;
                for (int k = 0; k < j; k++)
                {
                    Number *= j;
                    Number %= 10000000000;
                }
                Sum += Number % 10000000000;
                Sum %= 10000000000;
                Number = 1;
            }
            label1.Text = "Answer = " + Sum;
        }

最後の10桁を求めるということなので、10桁になるように10桁で割った余りを足すようにしました。全て足したものを答えとしました。

49問目

公差3330の等差数列1487, 4817, 8147は次の2つの変わった性質を持つ.

(i)3つの項はそれぞれ素数である.
(ii)各項は他の項の置換で表される.
1, 2, 3桁の素数にはこのような性質を持った数列は存在しないが, 4桁の増加列にはもう1つ存在する.

それではこの数列の3つの項を連結した12桁の数を求めよ.

private void button49_Click(object sender, EventArgs e)
        {
            int[] Number = new int[3];
            for (int i = 1; Number[0] < 10000; i++)
            {
                Number[0] = 1000 + i;
                for (int diff = 3330; ; diff++)
                {
                    for (int j = 1; j < 3; j++)
                    {
                        Number[j] = Number[j - 1] + diff;
                    }
                    if (Number[2] > 10000) break;
                    if (Sosuu(Number[0]) == true && Sosuu(Number[1]) == true && Sosuu(Number[2]) == true)
                    {
                        var list1 = new List<char>(Number[0].ToString());
                        var list2 = new List<char>(Number[1].ToString());
                        var list3 = new List<char>(Number[2].ToString());
                        list1.Sort();
                        list2.Sort();
                        list3.Sort();
                        if (list1.SequenceEqual(list2) && list2.SequenceEqual(list3))
                        {
                            string Answer = Number[0].ToString() + Number[1].ToString() + Number[2].ToString();
                            label1.Text = "Answer = " + Answer;
                        }
                    }
                }
            }
        }

公差を1ずつ増やしながら、3つの項が10000より下であること、求めた3つの項が含んでいる数字が同じことを調べ、3つの項を連結したものを答えとしました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#のコンソールアプリで、特定のキーが押されているか調べる

概要

C#のコンソールアプリケーションで、無限ループ中に特定のキーが押されたら終了処理をしてアプリケーションを終わる…という処理がしたかったが、ググっても、ググっても、ウインドウアプリでのやり方しか出てこなかったので、出来るようになった方法をポエムにまとめておく。

※コメント欄にて、もっと簡単な方法を教えて貰った。

前提

クラス宣言の直後にDLL読み込みを記述しておく。

class Program {
  [System.Runtime.InteropServices.DllImport("user32.dll")]
  private static extern short GetKeyState(int nVirtKey);
  ・
  ・
  ・

キー押下判断

while (GetKeyState(0x51) == 0) {
  // なにかしらの処理
}

これで、「Q」が押されるまでwhileループを廻り続ける。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

未経験SEが突然C#をふられた時のメモ

自己紹介

・Java検定2級に合格して喜んでたらC#をふられた
・相変わらずお先真っ暗

発端

「1週間後にC#を使った案件にいってもらうから準備しといてね」

突然の上司からのお告げ。
C#???Javaは????
プロジェクトが週単位でころころ変わるのは勘弁していただきたい
文句言ってもしょうがないのでいろいろ調査しました。

調査

C#(シーシャープ)
Microsoftがつくったプログラム言語。
JavaとC++のいいとこどりをした言語でJavaに似ててC++よりカンタン。
スマホアプリ、ゲーム、Windowsクライアント、ウェブサイト、データベース、クラウドなどいろいろカバーしているスゴイ言語。
開発環境は『Visual Studio』が多く使われていて、拡張子は『.cs』

.NET Framework(ドットネット フレームワーク)
Windowsに入ってるプログラムを動かしたり作ったりするときに使える便利な環境のこと。
ようするにwebサービスとかwebアプリケーションを作成するための開発環境、実行環境。
(Visual Studioとはちょっと違う?)

WPF(Windows Presentation Fondation)
.NET Frameworkに含まれるプレゼンテーション層技術。
画面系(GUI)をいろいろ弄りやすくしてくれる便利な技術で
柔軟性があるため、

・任意の形状のボタンを作成できる!
・背景に動画を流すことができる!
・動的に拡大・縮小ができる!  など。

XAML(ザムル)と呼ばれるXML形式の言語を用いてUIを記述することができ、
ロジック部分はC#で記述するという特徴がある。
わざわざ別々になってるのは、
『デザインとロジックを分離することでそれぞれの担当者が作業を進めやすくするため』らしい。

結果的に

事前調査してみたけど知識がフワフワ状態です。
C#に限ったことじゃないと思いますが、新しい言語を勉強するのって大変だと再確認しました。
実はこの記事書いてる時点ではこの案件はもう終了しているんですが
正直理解しきれていないところが多々あります・・・

まだまだ勉強しなくちゃいけないことが多いなぁ~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む