スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#によるLinq to Obj入門 (18) クエリ式(参考)

C#ではクエリ式なるものが使えて

var nums = Enumerable.Range(1, 10);
var q = from n in nums       
        where n % 3 == 1     
        foreach (var n in q) 
     Console.WriteLine(n);

などという処理が可能です。(2行目から4行目がクエリ式です。)
(実際にはコンパイル時にクエリ式は、拡張メソッド形式に変換されたうえでコンパイルされます。)
クエリ式についてはGushwellさんの『C#プログラミングレッスン』のLINQ to Object部分がとても参考になります。

http://gushwell.ifdef.jp/magArchive.html

せっかくですから、ソリューションの中にC#とF#のプロジェクトを混在させて、クエリ式関連だけC#のコードとして書いて、他の部分をF#で書いてみます。

ソリューションに3つのプロジェクトを作成します。
(1)F#のプロジェクトData1
内部にファイルEmpData.fsでその内容は次の通り

module EmpData

type Emp =
    {ID : int;//ID番号
     name : string;//名前
     height : float;//身長
     sex :int //0が男,1が女
     }
     member this.disp () =
        printfn "%2d %s %3.1f %A" this.ID this.name this.height this.sex

let EmpSeq = seq[{ID = 1;name = "Jhon";height = 168.3;sex = 0};
                {ID = 3;name = "Jhon";height = 161.1;sex = 0};
                {ID = 8;name = "Kim";height = 168.3;sex = 1};
                {ID = 2;name = "Sawyer";height = 168.7;sex = 0};
                {ID = 13;name = "Michael";height = 155.0;sex = 0};
                {ID = 19;name = "Revecca";height = 154.8;sex = 1};
                {ID = 11;name = "Hugo";height = 176.6;sex = 0};
                {ID = 21;name = "Maggie";height = 149.1;sex = 1};
                {ID = 7;name = "Ben";height = 157.6;sex = 0}]

type Sex =
    { S_ID :int;
      expInJ : string
      expInE : string}

let SexSeq = seq[{S_ID = 0;expInJ = "男";expInE = "Man"};
                 {S_ID = 1;expInJ = "女";expInE = "Woman"}
                 ] 

(2)C#のプロジェクトLinqData
上のプロジェクトData1を参照設定しておく。
内部にファイルLinqData.cx  でその内容は次の通り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LinqData
{
   public class MyQuery01c
    {
        public static IEnumerable<int> MyQuery01 (IEnumerable<int> arg) {

            var q = from p in arg
                    where p % 3 == 0
                    select 2*p;
            return q;
        }
    }

    public class MyQuery02c
    {
        public static IEnumerable<EmpData.Emp> MyQuery02(IEnumerable<EmpData.Emp> arg)
        {
            var q = from p in arg
                    where p.height >160.5
                    select p;
            return q;
        }
    }
   

(3)F#のプロジェクトLinqBlog2Main
上のプロジェクトData1とLinqDataを参照設定しておく。
内部にファイルmainLinq.fs
なお上の二つのプロジェクトをビルドしておく。(これでインテリセンスが使える)

open System

open System.Collections.Generic
open System.Collections
open System.Linq

let nums = seq[1;2;3;4;5;6;7]

nums
 |> LinqData.MyQuery01c.MyQuery01
 |> Seq.iter(fun x -> printfn "%d " x)
 
printfn ("-----------------------------")
 
EmpData.EmpSeq
 |> LinqData.MyQuery02c.MyQuery02
 |> Seq.map(fun p -> p.name)
 |> Seq.iter(fun x -> printfn "%s " x)

これでコンパイル実行すると次のように表示されます。

6
12
-----------------------------
Jhon
Jhon
Kim
Sawyer
Hugo
続行するには何かキーを押してください . . .

次にクエリとしてjoinを使ったものを使用してみます。
C#だけで使用する場合は匿名クラスが使えて便利なのですが、F#側から使うためには
クエリ結果格納用のクラスを作るか、タプルを利用する等の方法が必要となります。

○クエリ結果格納用のクラスを作る例

EmpData.fsの追加分

type Query3ResType(in_ID:int,in_name:string,in_sex:string) =
    member this.ID = in_ID
    member this.name = in_name
    member this.sex  = in_sex
   
-------------------------------------------------------------------
LinqData.csの追加分

public class MyQuery03c
{
        public static IEnumerable<EmpData.Query3ResType> MyQuery03(IEnumerable<EmpData.Emp> arg1, IEnumerable<EmpData.Sex> arg2)
        {
            var q = from emp in arg1
                    join sex in arg2 on emp.sex equals sex.S_ID
                    select new EmpData.Query3ResType(emp.ID, emp.name, sex.expInJ);
            return q;
        }
    }

-------------------------------------------------------------------
mainLinq.fsの追加分

LinqData.MyQuery03c.MyQuery03(EmpData.EmpSeq,EmpData.SexSeq)
 |> Seq.iter(fun x -> printfn "%2d:%s : %s" x.ID x.name x.sex)
 
-------------------------------------------------------------------
実行結果の一部

 1:Jhon : 男
 3:Jhon : 男
 8:Kim : 女
 2:Sawyer : 男
13:Michael : 男
19:Revecca : 女
11:Hugo : 男
21:Maggie : 女
 7:Ben : 男
続行するには何かキーを押してください . . .

○タプルを使用する場合

LinqData.csの追加分

    public class MyQuery04c
    {
        public static IEnumerable<Tuple<int, string, string>> MyQuery04(IEnumerable<EmpData.Emp> arg1, IEnumerable<EmpData.Sex> arg2)
        {
            var q = from emp in arg1
                    join sex in arg2 on emp.sex equals sex.S_ID
                    select new Tuple<int,string,string>(emp.ID, emp.name, sex.expInJ);
            return q;
        }
    }
   
-------------------------------------------------------------------
mainLinq.fsの追加分

LinqData.MyQuery04c.MyQuery04(EmpData.EmpSeq,EmpData.SexSeq)
 |> Seq.iter(fun (x,y,z) -> printfn "%2d:%s : %s" x y z)
 

-------------------------------------------------------------------
実行結果の一部

 1:Jhon : 男
 3:Jhon : 男
 8:Kim : 女
 2:Sawyer : 男
13:Michael : 男
19:Revecca : 女
11:Hugo : 男
21:Maggie : 女
 7:Ben : 男
続行するには何かキーを押してください . . .

ということで、いくぶん手間がかかるのですが、とりあえずクエリ式をC#で書いて、それをF#側から利用する方法の紹介でした。
とりあえずこれで「F#によるLinq to Obj入門」はおしまいです。
あと終わってないシリーズの続きとか並行処理についてのシリーズをやってみたいと思ってます。
知っていることを書くのでなく、勉強したことをまとめてみるというブログなので、不手際は多いのですが、ぼちぼち続けていきたいと思ってます。

スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

F#によるLinq to Obj入門 (17) Zip 遅延処理とそうでないもの

○Zip
 
Zipは二つのシークエンスから、同時に一つづつ取り出せて、それを組み合わせて新しいシークエンスを作ります。
 
次のようなシグネチャーとなります。
 
static member Zip : 
        first:IEnumerable<'TFirst> * 
        second:IEnumerable<'TSecond> * 
        resultSelector:Func<'TFirst, 'TSecond, 'TResult> -> IEnumerable<'TResult> 
 
引数のfirst,secondはそれぞれのシークエンスで、resultSelectorが「同時に取り出した要素をどう処理して新しい値を作るかを指示する関数」となります。
 
> Enumerable.Zip([1;2;3],[4;5;6],(fun x y -> x + y));;
val it : seq<int> = seq [5; 7; 9]
 
> Enumerable.Zip([1;2;3],[4;5;6],(fun x y -> (x,y)));;
val it : seq<int * int> = seq [(1, 4); (2, 5); (3, 6)]
 
もちろん、第一引数のインスタンスメソッドとしても使えます。
 
> ([1;2;3]).Zip([4;5;6],(fun x y -> x + y));;
val it : seq<int> = seq [5; 7; 9]
 
要素数が不ぞろいは場合は短い方の最後まで計算します。
 
> ([1;2]).Zip([4;5;6],(fun x y -> x + y));;
val it : seq<int> = seq [5; 7]
 
一方Seqモジュールの方のSeq.zipは二つのシークエンスから、ひとつづつ取り出して、タプルを作って返します。
 
> Seq.zip;;
val it : (seq<'a> -> seq<'b> -> seq<'a * 'b>) = <fun:clo@2>
 
> Seq.zip [1;2;3] [4;5;6];;
val it : seq<int * int> = seq [(1, 4); (2, 5); (3, 6)]
 
> Seq.zip [1;2] [4;5;6];;
val it : seq<int * int> = seq [(1, 4); (2, 5)]
 
Seq.zip3もあります。
 
> Seq.zip3 [1;2;3] [4;5;6] [7;8;9];;
val it : seq<int * int * int> = seq [(1, 4, 7); (2, 5, 8); (3, 6, 9)]
 
おまけで、Linqで使われるメソッドを遅延処理とそうでないものについて一覧にしてみました。
遅延評価には○をつけてあります。
 
Aggregate        
All                
Any
Average
○Cast            
○Concat            
Contains
Count
○DefaultIfEmpty        
○Distinct            
ElementAt        
ElementAtOrDefault
○Empty    
○Except
First
FirstOrDefault
○GroupBy
○GroupJoin
○Intersect
○Join
Last
LastOrDefault
LongCount
Max
Min
○OfType
○OrderBy
○OrderByDescending
○Range
○Repeat
○Reverse
○Select
○SelectMany
SequenceEqual
Single
SingleOrDefault
○Skip
○SkipWhile
Sum
○Take
○TakeWhile
○ThenBy
○ThenByDescending
ToArray
ToDictionary
ToList
ToLookup
○Union
○Where      

テーマ : プログラミング
ジャンル : コンピュータ

F#によるLinq to Obj入門 (16) Range Repeat Empty

○Range
 
Range は開始の整数値と個数を与えて、整数のシークエンスを返します。
 
> Enumerable.Range(2, 10)
 |> Seq.iter (fun x -> printf "%d :" x);;
 
2 :3 :4 :5 :6 :7 :8 :9 :10 :11 :val it : unit = ()
 
自分で定義してみると、例えば次の様になります。
 
> let myRange init num = Seq.unfold
                         (fun (v,counter) ->
                               if counter < num then 
                                    Some(v,(v+1,counter+1))
                               else None)           
                         (init,0);;
 
val myRange : int -> int -> seq<int>
 
使用例
 
> myRange 2 5
|> Seq.iter (fun x -> printf "%A :" x);;
 
2 :3 :4 :5 :6 :val it : unit = ()
> myRange -1 5
|> Seq.iter (fun x -> printf "%A :" x);;
 
-1 :0 :1 :2 :3 :val it : unit = ()
 
なおSeq.unfoldについての詳細はこちらをご覧ください。
http://fsharpandenglishword.blog83.fc2.com/blog-entry-53.html
 
Seqモジュールでは例えばSeq.initを用いると同じようなことが実現できます。
 
Seq.init toExclusive (f : int -> 'a)で、0から(toExclusive-1)までの整数値がfに渡され、fを適用した結果のシークエンスが返されます。
 

 
> Seq.init 3 (fun i -> i);;
val it : seq<int> = seq [0; 1; 2]
 
> Seq.init 3 (fun i -> 2.0*(float)i);;
val it : seq<float> = seq [0.0; 2.0; 4.0]
 
 
○Repeat
 
Repeatは値を回数分繰り返すシークエンスを生成します。
 
> Enumerable.Repeat(-1,4);;
val it : seq<int> = seq [-1; -1; -1; -1]
 
> Enumerable.Repeat(1.0,3);;
val it : seq<float> = seq [1.0; 1.0; 1.0]
 
Seq.initを利用して自分で定義してみると、例えば次の様になります。
 
> let myRepeat v count =
    Seq.init count (fun _ -> v);;
 
val myRepeat : 'a -> int -> seq<'a>
 
> myRepeat -1 4;;
val it : seq<int> = seq [-1; -1; -1; -1]
 
○Empty
 
Emptyは、指定された型の空のシーケンスを返します
 
> let myIntEmpty = Enumerable.Empty<int>();;
 
val myIntEmpty : seq<int> = [||]
 
型注釈の形でも使えます。
 
> let myIntEmpty2:seq<int> = Enumerable.Empty();;
 
val myIntEmpty2 : seq<int> = [||]
 
Seqモジュールでは次のようにできます。
 
> let myIntEmpty3 = Seq.empty<int>;;
val myIntEmpty3 : seq<int>
> myIntEmpty3;;
val it : seq<int> = seq []
 
また次のようにもできます。
 
> let myIntEmp3:seq<int> = seq[];;
val myIntEmp3 : seq<int> = []
 
> let myIntEmp4 = seq<int>[];;
val myIntEmp4 : seq<int> = []

テーマ : プログラミング
ジャンル : コンピュータ

F#によるLinq to Obj入門 (15) Cast OfType

今はあまり使われませんが、.NET 2.0の時代ArrayListというクラスがありました。
例えば次の様に使います。
 
> open System.Collections.Generic
open System.Linq
open System.Collections;;
 
> let aL = new ArrayList();;
val aL : ArrayList
 
> aL.Add(3);aL.Add(4);aL.Add(5);;
 
> aL.Count;;
val it : int = 3
 
ここでLINQのメソッドを適用してみようとしても、大部分のメソッドは適用できません。
これはArrayListがIEnumerableインターフェイスは備えていても、IEnumerable<>インターフェイスを備えていないからです。
ここで、Castの出番です。
シグネチャーは次のようになります。
 
static member Cast : 
        source:IEnumerable -> IEnumerable<'TResult> 
        
(いつも通り第一引数のインスタンスメソッドとしても使えます。)
 
つまりIEnumerable型から、IEnumerable<>型への変換関数です。
 
使ってみます。
 
> let castedAL = aL.Cast<int>();;
val castedAL : seq<int>
 
> castedAL.Select(fun x -> 2*x);;
val it : seq<int> = seq [6; 8; 10]
 
通常はつないで使います。
 
> aL.Cast<int>().Select(fun x -> 2*x);;
val it : seq<int> = seq [6; 8; 10]
 
さて、ArrayListはobj型のcollectionです。3,4,5とint型の値を入れましたが、ここでstring型の値を追加してみます。
 
> aL.Add("six");;
 
さて先ほどのCastを使用してみます。
 
> aL.Cast<int>().Select(fun x -> 2*x);;
val it : seq<int> = Error: 指定されたキャストは有効ではありません。
 
あたりまえですがstrignをintにキャストできないので、エラーがでてきます。
 
このような場合、OfTypeを使うと、指定された型に基づいて IEnumerable の要素をフィルター処理した上でIEnumerable<>型に変換してくれます。
 
シグネチャーは次の通りです。
 
static member OfType : 
        source:IEnumerable -> IEnumerable<'TResult> 
 
(いつも通り第一引数のインスタンスメソッドとしても使えます。)
 
では使ってみます。
 
> aL.OfType<int>().Select(fun x -> 2*x);;
val it : seq<int> = seq [6; 8; 10]
 
なおSeqモジュールでCastに対応するのはSeq.castです。
 
> Seq.cast;;
val it : (IEnumerable -> seq<'a>) = <fun:clo@10>
 
OfTypeに対応するものはありません。
 
ということで、レガシーコード向けのメソッドCase,OfTypeの紹介でした。

テーマ : プログラミング
ジャンル : コンピュータ

F#によるLinq to Obj入門 (14) ToDictionary ToLookup

サンプルのためのレコード型とデータを準備しておきます。(いままでと同じものです。) 
 
type Emp = 
    {ID : int;//ID番号 
     name : string;//名前 
     height : float;//身長 
     sex :int //0が男,1が女 
     } 
     member this.disp () = 
        printfn "%2d %s %3.1f %A" this.ID this.name this.height this.sex 
 
サンプルデータを定義しておきます。 
 
Jhonが2人いることに留意しておいてください。 
 
let EmpSeq = seq[{ID = 1;name = "Jhon";height = 168.3;sex = 0}; 
                {ID = 3;name = "Jhon";height = 161.1;sex = 0}; 
                {ID = 8;name = "Kim";height = 168.3;sex = 1}; 
                {ID = 2;name = "Sawyer";height = 168.7;sex = 0}; 
                {ID = 13;name = "Michael";height = 155.0;sex = 0}; 
                {ID = 19;name = "Revecca";height = 154.8;sex = 1}; 
                {ID = 11;name = "Hugo";height = 176.6;sex = 0}; 
                {ID = 21;name = "Maggie";height = 149.1;sex = 1}; 
                {ID = 7;name = "Ben";height = 157.6;sex = 0}] 
 
 
ToDictionaryは「シークエンス」と、「シークエンスの要素からキーを作る関数」のタプルを引数にして、Dictionaryを返します。
Dictionaryについてはを参照してください。
シンタックスは次の通りです。
 
static member ToDictionary : 
        source:IEnumerable<'TSource> * 
        keySelector:Func<'TSource, 'TKey> -> Dictionary<'TKey, 'TSource> 
 
使用上の注意としては、「シークエンスの要素からキーを作る関数」によって生成される値が「一意である」こと、すなわち「重複がない」ようにするということです。もし2つ以上の要素に対して重複するキーを生成すると例外が発生します。
 

EmpSeqからIDをキーにしてDictionaryを作ります。
 
> let empDic = Enumerable.ToDictionary(EmpSeq,(fun p -> p.ID));;
 
val empDic : Dictionary<int,Emp> =
  dict
    [(1, {ID = 1;
          name = "Jhon";
          height = 168.3;
          sex = 0;}); (3, {ID = 3;
                           name = "Jhon";
                           height = 161.1;
                           sex = 0;}); (8, {ID = 8;
                                            name = "Kim";
                                            height = 168.3;
                                            sex = 1;}); (2, {ID = 2;
                                                             name = "Sawyer";
                                                             height = 168.7;
                                                             sex = 0;});
     (13, {ID = 13;
           name = "Michael";
           height = 155.0;
           sex = 0;}); (19, {ID = 19;
                             name = "Revecca";
                             height = 154.8;
                             sex = 1;}); (11, {ID = 11;
                                               name = "Hugo";
                                               height = 176.6;
                                               sex = 0;});
     (21, {ID = 21;
           name = "Maggie";
           height = 149.1;
           sex = 1;}); (7, {ID = 7;
                            name = "Ben";
                            height = 157.6;
                            sex = 0;})]
 
> empDic.[11];;
val it : Emp = {ID = 11;
                name = "Hugo";
                height = 176.6;
                sex = 0;}
                
 

名前をキーにしてDictionary作成を試みてみます。
 
> let empDic2 = Enumerable.ToDictionary(EmpSeq,(fun p -> p.name));;
System.ArgumentException: 同一のキーを含む項目が既に追加されています。
 
というようにJhonが2人いるので、例外が発生します。
このような同一キーに複数の値が対応する可能性があるときはDictionaryクラスの代わりにLookupクラスを使います。
 
ではLookupクラスの紹介です。
これはSystem.Linq名前空間で定義されており、次のようなシグネチャーをもちます。
 
type Lookup<'TKey, 'TElement> =  
    class
        interface ILookup<'TKey, 'TElement>
        interface IEnumerable<IGrouping<'TKey, 'TElement>>
        interface IEnumerable
    end
 
Enumerable.ToLookupメソッドを使って、名前をキーにしてLookupオブジェクトを生成してみます。
(引数はToDictionaryと同じです。)
 
> let empLU = Enumerable.ToLookup(EmpSeq,(fun p -> p.name));;
val empLU : ILookup<string,Emp>
 
キーから、値を求めてみます。
 
> empLU.["Jhon"];;
val it : seq<Emp> = seq [{ID = 1;
                          name = "Jhon";
                          height = 168.3;
                          sex = 0;}; {ID = 3;
                                      name = "Jhon";
                                      height = 161.1;
                                      sex = 0;}]
                                      
結果がseq<Emp>型になっていることに留意してください。
 
> empLU.["Hugo"];;
val it : seq<Emp> = seq [{ID = 11;
                          name = "Hugo";
                          height = 176.6;
                          sex = 0;}]
                                      

テーマ : プログラミング
ジャンル : コンピュータ

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。