nomurabbitのブログ

nomurabbitのブログはITを中心にした技術ブログです。

nomurabbitのブログ

yield returnを表面的に理解

<はじめに>
LINQについて調べていると、ところどころに出てくる

yield return hogehoge...

この「yield」というのがいまいちピンとこない!
そこでmsdnのドキュメントを眺めていると、いいのがありました!

反復子 (C# および Visual Basic)
Yield または yield return ステートメントに到達すると、コードの現在の位置が保持されます。 実装はその位置から反復子関数が呼び出されるときに再起動されます。

うーん…文章を眺めていてもわかりづらいので、実際に書いてみることに。


<プログラム1>

class Program
{
    static void Main(string[] args)
    {
        foreach (int number in SomeNumbers())
        {
            Console.Write(number.ToString() + " ");
        }
    }

    public static System.Collections.IEnumerable SomeNumbers()
    {
        yield return 3;
        yield return 5;
        yield return 8;
    }
}


<実行結果1>
f:id:nomurabbit:20131018000345p:plain


ほうほう!

ということは、こんな感じにしたら…


<プログラム2>

class Program
{
    static void Main(string[] args)
    {
        foreach (int number in SomeNumbers())
        {
            Console.Write(number.ToString() + " ");
        }
    }

    public static System.Collections.IEnumerable SomeNumbers()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}


<実行結果2>
f:id:nomurabbit:20131018000541p:plain


なるほど、そういうことか!


<まとめ>
yield returnを表面的な理解の元日本語で書くとすると、
「呼び出し元からループで呼ばれると、
 呼ばれた側は前回呼ばれたyieldの位置を覚えていて、
 その次の文から処理を再開する」
ということですね。

拡張メソッドって何ぞ?

<はじめに>
msdnより
拡張メソッド (C# プログラミング ガイド)
拡張メソッドを使用すると、新規の派生型の作成、再コンパイル、または元の型の変更を行うことなく既存の型にメソッドを "追加" できます。

えーっ!?そんなことできるんですか!?

自分の不勉強をかみしめながら調べてみると、
拡張メソッドとしてstaticメソッドを用意しておくと、
あたかも他のクラスのインスタンスメソッドのように扱えるよ!ってことらしい。


<プログラム>
■拡張メソッドの実装

class Program
{
    static void Main(string[] args)
    {
        string morning = "Morning";

        morning.Hoge();

        morning.Fuga("Good");

        OtherClass otherClass = new OtherClass();

        otherClass.Foo();
    }
}

static class Kakucho
{
    public static void Hoge(this string x)
    {
        Console.WriteLine(x);
    }

    public static void Fuga(this string x, string y)
    {
        Console.WriteLine(y + " " + x );
    }

    public static void Foo(this OtherClass x)
    {
        Console.WriteLine(x.message);
    }
}

class OtherClass
{
    public string message = "Hello World";
}


<実行結果>
f:id:nomurabbit:20131017085351p:plain


<まとめ>
拡張メソッドを定義するときに、第一引数が対象のクラス、第二引数以降がメソッド呼び出し時の引数になるので注意。
慣れないと混乱するかもしれないけど難しいことはないです。

同じクラスに対して名前空間ごとに複数の拡張メソッドを定義することができるので、
例えば同じクラスを別の名前空間で使う時に、拡張メソッドを使って振る舞いを分けることができたりするのかな?

delegateを調べて試してみた

<はじめに>
delegateと聞くと、「イベントとメソッドを紐づけるときに使う何か」という程度の認識だったのですが、このままではいかん!と思って調べてみたらなかなか面白いではありませんか。

そこで、delegate型で少し遊んでみたのが下記

①delegate型のインスタンスを引数にして試してみた
ジェネリックなdelegate型を試してみた
③匿名メソッドでdelegate型を試してみた


<プログラム>

class Program
{
    static void Main(string[] args)
    {
        //-----------------------------------------
        // 変数の定義
        //
        int[] x = { 1, 3, 5, 7, 11 };
 
        string name = "nomurabbit";
 
        //-----------------------------------------
        // delegate型のインスタンス
        //
        Hoge hogeGetDouble = new Hoge(GetDouble);
 
        Console.WriteLine("//// delegate型のインスタンス----");
 
        TestMethod(hogeGetDouble, x);
 
        //-----------------------------------------
        // ジェネリックなdelegate
        //
        Fuga<string> fugaGetMessage = new Fuga<string>(GetMessage);
 
        Console.WriteLine("//// ジェネリックなdelegate------");
 
        fugaGetMessage(name);
 
        //-----------------------------------------
        // 匿名メソッドでdelegate
        //
        Console.WriteLine("//// 匿名メソッドでdelegate------");
 
        TestMethod(delegate(int y) { return y * 4; }, x);
    }
 
    #region delegateの定義

    delegate int Hoge(int x);
 
    delegate void Fuga<T> (T x);
 
    #endregion
 
    #region delegateに渡すメソッド
 
    static int GetDouble(int x)
    {
        return x * 2;
    }
 
    static void GetMessage(string tempMessage)
    {
        Console.WriteLine("My name is " + tempMessage);
    }
 
    #endregion
 
    #region delegate型のインスタンスに第二引数を渡すメソッド
 
    static void TestMethod (Hoge h, int[] x)
    {
        foreach (var item in x)
        {
            Console.WriteLine(h(item).ToString());
        }
    }
 
    #endregion
}


<実行結果>
f:id:nomurabbit:20131016235259p:plain


<まとめ>
なんと言ってもメソッドをインスタンスにして引数扱いできちゃうのが素敵だと思います。関数型っぽい(?)ですよね。

あとは、匿名メソッドが便利!メソッド名決めるのってなんだかんだ悩みますし、
delegate型に渡すちょっとしたメソッドが匿名で書けるのは気持ちいい!

C#のDisposeメソッドとデストラクタが呼び出されるのはいつ?

<はじめに>
IDisposableインターフェースを実装したクラスを使用する場合、
using ステートメントを使ってリソースの解放を担保します。
そこで気になるのがDisposeメソッドとデストラクタの呼び出されるタイミング。

違うの?

一緒なの?

そこで、調べてみました。


<プログラム1>
■Dispose、デストラクタ呼び出しテスト

class Program
{
    static void Main(string[] args)
    {
        using (DisposeTest dTest = new DisposeTest())
        {
            Console.WriteLine("usingステートメントに入ったよ。");
        }
        Console.WriteLine("usingステートメントにから出たよ。");
    }
}

class DisposeTest : IDisposable
{
    ~DisposeTest()
    {
        Console.WriteLine("デストラクタが呼びだされたよ。");
    }

    public void Dispose()
    {
        Console.WriteLine("Disposeが呼びだされたよ。");
    }
}


<実行結果1>


usingステートメントを抜けたタイミングでDisposeメソッドが呼び出されるのは
想像通りだったのですが、デストラクタが呼び出されるのはもっと後なんですね。
ちょっと調べてみると下記のような記述が…


デストラクタ (C# プログラミング ガイド)
デストラクタがいつ呼び出されるかはガベージ コレクタによって決定されるため、プログラマは制御できません。


ん?ってことはGC呼び出せばそのタイミングでデストラクタ呼ばれるんじゃ…?
そう思ってプログラムを下記のように修正


<プログラム2>
■Dispose、デストラクタ呼び出しテスト_GC呼び出しver

class Program
{
    static void Main(string[] args)
    {
        using (DisposeTest dTest = new DisposeTest())
        {
            Console.WriteLine("usingステートメントに入ったよ。");
        }

        Console.WriteLine("usingステートメントにから出たよ。");

        // 下記2行を追加
        GC.Collect();
        Console.WriteLine("GCを実行したよ。デストラクタは呼び出されてるよね?");
    }
}

class DisposeTest : IDisposable
{
    ~DisposeTest()
    {
        Console.WriteLine("デストラクタが呼びだされたよ。");
    }

    public void Dispose()
    {
        Console.WriteLine("Disposeが呼びだされたよ。");
    }
}


<実行結果2>


<まとめ>
思ってたタイミングと違う!

やっぱりmsdnに書いてある通り、プログラムからは制御出来ませんでした。
正直デストラクタ使ってどうこうする機会はあまりないのですが、
興味本位で調べてみた結果は以上です。

MySQLからDBIx::Classで取ってきたデータをJavascriptで取得してみよう!

<はじめに>
3層構造かつmemcachedの練習です。
1)MySQLに格納されているデータをDBIx::Classで取得します。
2)無意味に一旦memcachedに格納した後に取り出してみます。
3)XML::SimpleやらJSONやらで加工してみます。
4)1)〜3)で作ったデータをjavascriptで取得します。


<プログラムについて>
[サーバー側]
DBIx::ClassMySQLにアクセス!

 10 #---------------------------------------------------------------------------- 
 11 # get Data from MySQL 
 12 #
 13 my $schema = MyApp::Schema->connect('dbi:mysql:ASUKA','user','pass');
 14 
 15 $schema->storage->dbh->do("SET names utf8");
 16 
 17 my @all_TODOFUKEN    = $schema->resultset('MDTDFKN')->all;
 18 my @tempHashArray    = ();
 19 
 20 foreach $todofuken (@all_TODOFUKEN)
 21 {
 22     my %tempHash = ('TODOFUKEN_ID'       => decode("utf8", $todofuken->TODOFUKEN_ID)       ,
 23                     'TODOFUKEN_MEI'      => decode("utf8", $todofuken->TODOFUKEN_MEI)      ,
 24                     'TODOFUKEN_MEI_KANA' => decode("utf8", $todofuken->TODOFUKEN_MEI_KANA));
 25     push(@tempHashArray, \%tempHash);
 26 }

■無意味にmemcachedに出し入れしてみる

 28 #----------------------------------------------------------------------------
 29 # use Memcached
 30 #
 31 my $key     = "hash";
 32 my $value   = \@tempHashArray;
 33 my $expires = 3600;
 34 my $memcached = Cache::Memcached->new(
 35                 {
 36                     servers => ["127.0.0.1:11211"],
 37                     compress_threshold => 10_000
 38                 });
 39
 40 $memcached->add($key, $value, $expires);
 41 
 42 my $tempData = $memcached->get($key); 
 43 my @tempData = @$tempData;

XMLっぽく加工(XMLについては著しく勉強不足…)

 50 #----------------------------------------------------------------------------
 51 # Make XML data
 52 #
 53 print "\n\nXML DATA---------------------------------------------------\n\n";
 54 
 55 my $xs       = XML::Simple->new();
 56 
 57 foreach $_ (@tempData)
 58 {
 59     my $item = $xs->XMLout($_);
 60 
 61     print encode("utf8", $item);
 62 }

■JSON形式に加工

 64 #----------------------------------------------------------------------------
 65 # Make JSON data
 66 #
 67 print "\n\nJSON DATA--------------------------------------------------\n\n";
 68 
 69 foreach $_ (@tempData)
 70 {
 71     my $item = encode_json($_);
 72 
 73     print $item, "\n";
 74 }

[クライアント側]
Javascriptでサーバー側からデータを取得

  7 httpRequest = false;
  8 httpRequest = new XMLHttpRequest();
  9 httpRequest.overrideMimeType('text/xml');
 10 
 11 function request()
 12 {
 13     httpRequest.abort();
 14     httpRequest.open("GET","http://hogehoge/fuga.cgi",true);
 15     httpRequest.onreadystatechange = function()
 16     {
 17         if(httpRequest.readyState == 4)
 18         {
 19             if(httpRequest.status == 200)
 20             {
 21                 window.alert("Success!!");
 22                 document.getElementById('pageText').value = httpRequest.responseText;
 23             }
 24         }
 25     }
 26     httpRequest.send(null);
 27 }


<まとめ>
DBIx::Class使ってみると、データ取得した後にDBのカラム名をキーに
hashを作りたくなるんだけど、べた書きだとあんまりかっこよくない。
カラム名のデータはどこからか取れないのかなー?なんて思ったり。
今回はそこまでやりませんでしたけど、javascriptでデータ取得した後に
XMLなりJSONなりをどうやって加工しようかな?なんて思ったり。
このネタでもう少し遊べそうです。

F# で熱伝導の数値解析を頑張ってみた

F# Advent Calendarの18日目です。

F# Advent Calendar 2012 : ATND

さて、F#で何かしてみよう!

ということで、

今回は熱伝導の数値解析(のようなも)をしてみました!


<はじめに>

数値解析をするにあたり、できるだけ簡素化して考えてみました。

ざっくりとした仮定はこんな感じ。

1)対象の範囲は20 * 20の二次元配列とする

2)対象の範囲の3辺は壁、残る1辺を熱源とする

3)壁と熱源以外の要素の値はその要素の上下左右の要素の平均とする


<プログラムについて>

文字にすると仮定がわかりにくいですが、プログラムの流れとしては下記の通り

1)初期値の配列を生成する

2)関数に配列を渡し、仮定3)に基づき計算の上新しい配列を返す

3)2)の繰り返し

実際のソースの一部を以下に示します。


■初期値の作成(※ 熱源は9、壁、その他の要素は0とします。)

//----------------------------------------------------------
// 定数の定義
//
let width  = 20
let height = 20

//----------------------------------------------------------
// 熱源・壁を定義するメソッド
//
let heatSource (tempHeatLevel : int) (tempWidth : int) = Array.create tempWidth tempHeatLevel

let heatSource_heatLevelIs_0 (tempWidth : int) = heatSource 0 tempWidth
let heatSource_heatLevelIs_9 (tempWidth : int) = heatSource 9 tempWidth

//----------------------------------------------------------
// 配列の初期化メソッド
//
let rec initializeArray (tempWidth : int) (tempHeight : int) =
  if tempHeight = 1
    then
      heatSource_heatLevelIs_9 tempWidth
  else
      Array.append (initializeArray tempWidth (tempHeight - 1)) (heatSource_heatLevelIs_0 tempWidth)


■引数の配列から新しい配列を生成して返す(メインの処理)

//----------------------------------------------------------
// メインの演算メソッド(tempIndexには(array.Length - 1) - width * 2 を指定)
//
let rec getResultArray (tempWidth : int) (tempIndex : int) (tempArray : int[]) =
  if tempIndex = 0 
    then
      [|0|]
  elif ((tempIndex % tempWidth) = 0) || ((tempIndex % tempWidth) = (tempWidth - 1))
    then
      Array.append
        (getResultArray tempWidth (tempIndex - 1) tempArray)
        [|0|]
  else
    if ((double tempArray.[tempIndex + tempWidth + 1] +
         double tempArray.[tempIndex + tempWidth - 1] +
         double tempArray.[tempIndex + tempWidth * 2] +
         double tempArray.[tempIndex]                 +
         2.0) / 4.0 * rateOfHeatCondactor) < 9.0
      then
        Array.append
          (getResultArray tempWidth (tempIndex - 1) tempArray)
          [|int (Operators.round (double tempArray.[tempIndex + tempWidth + 1] +
                                  double tempArray.[tempIndex + tempWidth - 1] +
                                  double tempArray.[tempIndex + tempWidth * 2] +
                                  double tempArray.[tempIndex]                 +
                                  2.0) / 4.0 * rateOfHeatCondactor)|]
    else
        Array.append
          (getResultArray tempWidth (tempIndex - 1) tempArray)
          [|9|]

//----------------------------------------------------------
// メインの演算結果に境界条件を追加するメソッド
//
let getResultArrayWithBoundaryCondition (tempFiestRow : int[]) (tempLastRow : int[]) (tempArray : int[]) =
  Array.append
    tempFiestRow
    (Array.append
       tempArray
       tempLastRow)

let getResultArrayWithBoundaryCondition_firstRowIs_9_lastRowIs_0 (tempArray : int[]) =
  getResultArrayWithBoundaryCondition (heatSource_heatLevelIs_9 width) (heatSource_heatLevelIs_0 width) tempArray


■メインの処理の繰り返し

let rec getResult (tempLoopCount : int) (tempArray : int[]) =
  if tempLoopCount = 0 
    then
      getResultArrayWithBoundaryCondition_firstRowIs_9_lastRowIs_0 (getResultArray width (tempArray.Length - 1 - width * 2) tempArray)
  else
      getResultArrayWithBoundaryCondition_firstRowIs_9_lastRowIs_0 (getResultArray width (tempArray.Length - 1 - width * 2) (getResult (tempLoopCount - 1) tempArray))


■このほかにも熱伝導率的なパラメータなんかも付け加えています。


<結果>

処理の結果をまとめるとこんな感じになります。

■5回処理後

■10回処理後

■20回処理後

■30回処理後

処理を繰り返すにつれて、熱源(底辺)から熱が伝っていくのがなんとなくわかりますでしょうか?


<まとめ>

普段業務では一切F#は書かないので、

AdventCalenderはF#で遊ぶとてもいい機会になりました。

慣れていない道具を使うのは大変ですけど、

いろいろ調べながらプログラムを書く過程もまた楽しいですね。

ますますF#の魅力にとりつかれそうです。

私の記事は以上となります。

明日のカレンダーはigetaさんです!

楽しみですねー!