スポンサーサイト

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

F#によるLinq to Obj入門 (7) GroupBy

まずはサンプル用のレコード型とシークエンスを定義しておきます。
 
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}]
 
GroupByは「シークエンスの項に対して、キーを返す関数」を渡して、その値が等しいものをグループにします。
 
例 
 
3で割った余りでグループ分けする。
 
> seq[3;2;6;0;7].GroupBy(fun x -> x % 3);;
val it : seq<IGrouping<int,int>> = seq [seq [3; 6; 0]; seq [2]; seq [7]] //###
 
シークエンスの項に対して関数を適用すると0,2,0,0,1となるので、この値によってグループ分けされています。結果はseq<seq<int>>型のような気がしますが、実はそれは間違っていてseq<IGrouping<int,int>>と表示されています。名前を付けてもっと調べてみます。
 
> let t =  seq[3;2;6;0;7].GroupBy(fun x -> x % 3)
let t0 = t.ElementAt(0)
let t1 = t.ElementAt(1)
let t2 = t.ElementAt(2);;
 
val t : seq<IGrouping<int,int>>
val t0 : IGrouping<int,int>
val t1 : IGrouping<int,int>
val t2 : IGrouping<int,int>
 
これでt0には上の###部分でseq [3; 6; 0]と表現された部分が束縛されているはずです。
実はIGouping型は値のシークエンスを保存している以外に、キーを保存しており、これはKeyプロパティで求めることができます。(ただこのキーは###部分では表示されていません。)
 
> t0.Key;;
val it : int = 0
//この0は関数を適用したとき得たキーで、余りが0のグループということになります。
 
また、値もキーもintなので型がIGrouping<int,int>になって、紛らわしいですが、最初のintがキーの型で、2番目のintが値の型です。
 
例えばt0の要素部分を表示するには,IGouping<_,int>はseq<int>,IEnumerableを継承していることを利用すると
 
> Seq.iter (fun x -> printf "%2d " x) t0;;
 3  6  0 val it : unit = ()
 
とできます。
 

上のEmpSeqで身長150未満、150以上165未満、165以上でグループ分けする。
 
> EmpSeq.GroupBy(fun p -> if p.height < 150.0 then 0
                        elif 150.0 <= p.height && p.height < 165.0 then 1 
                        else 2);;
 
val it : seq<IGrouping<int,Emp>> =
  seq
    [seq [{ID = 1;
           name = "Jhon";
           height = 168.3;
           sex = 0;}; {ID = 8;
                       name = "Kim";
                       height = 168.3;
                       sex = 1;}; {ID = 2;
                                   name = "Sawyer";
                                   height = 168.7;
                                   sex = 0;}; {ID = 11;
                                               name = "Hugo";
                                               height = 176.6;
                                               sex = 0;}];
     seq [{ID = 3;
           name = "Jhon";
           height = 161.1;
           sex = 0;}; {ID = 13;
                       name = "Michael";
                       height = 155.0;
                       sex = 0;}; {ID = 19;
                                   name = "Revecca";
                                   height = 154.8;
                                   sex = 1;}; {ID = 7;
                                               name = "Ben";
                                               height = 157.6;
                                               sex = 0;}];
     seq [{ID = 21;
           name = "Maggie";
           height = 149.1;
           sex = 1;}]]
 
なお身長によってキーである0,1,2を対応させて、三種類にグループ分けしました。
 
 
それでは、男女別に身長の平均を表示してみます。
 
> let EmpSG = EmpSeq.GroupBy(fun p -> p.sex);;
val EmpSG : seq<IGrouping<int,Emp>>
 
> for sqsg in EmpSG do
    printfn "sex = %d ave = %A" sqsg.Key ((seq{ for p in sqsg  -> p.height }).Average());;
 
sex = 0 ave = 164.55
sex = 1 ave = 157.4
val it : unit = ()
 
 タプルで返すようにしてみます。
 
 > let res = EmpSeq.GroupBy(fun p -> p.sex).
                   Select(fun (sqg:IGrouping<int,Emp>)  -> (sqg.Key,(seq{ for p in sqg  -> p.height }).Average()));;
 
val res : seq<int * float>
 
> res;;
val it : seq<int * float> = seq [(0, 164.55); (1, 157.4)]
 
 
次にSeqモジュールの方のGroupByを紹介しておきます。
 
EmpSeq
|> Seq.groupBy(fun p -> p.sex);;
 
val it : seq<int * seq<Emp>> =
  seq
    [(0, seq [{ID = 1;
               name = "Jhon";
               height = 168.3;
               sex = 0;}; {ID = 3;
                           name = "Jhon";
                           height = 161.1;
                           sex = 0;}; {ID = 2;
                                       name = "Sawyer";
                                       height = 168.7;
                                       sex = 0;}; {ID = 13;
                                                   name = "Michael";
                                                   height = 155.0;
                                                   sex = 0;}; ...]);
     (1, seq [{ID = 8;
               name = "Kim";
               height = 168.3;
               sex = 1;}; {ID = 19;
                           name = "Revecca";
                           height = 154.8;
                           sex = 1;}; {ID = 21;
                                       name = "Maggie";
                                       height = 149.1;
                                       sex = 1;}])]
 
Linq版と返り値の型が異なることに注意してください。こちらは
seq<int * seq<Emp>>型です。
「キー」と「それに属する値のシークエンス」のタプルで返ります。
こちら側では男女別と身長の平均は、すんなり求めることができて次の様になります。
 
> EmpSeq
|> Seq.groupBy(fun p -> p.sex)
|> Seq.map (fun (key,sqp) -> (key, Seq.map(fun p -> p.height) sqp ))
|> Seq.map (fun (key,sqh) -> (key, Seq.average sqh));;
 
val it : seq<int * float> = seq [(0, 164.55); (1, 157.4)]
 
なおSeq.averageByを用いると次の様になります。
 
 > EmpSeq
|> Seq.groupBy(fun p -> p.sex)
|> Seq.map (fun (key,sqp) -> (key, Seq.averageBy (fun p -> p.height) sqp )) ;;
val it : seq<int * float> = seq [(0, 164.55); (1, 157.4)]
 
スポンサーサイト

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

コメントの投稿

非公開コメント

回避方法?

「型が前もって分からん」と言っているので、
(sqg:IGrouping<int,Emp>)
と型注釈を付けてやればいいと思います。

余計なお世話

Seq版、
Seq.averageBy が使えるので、map は、1つでOK

No title

どもアドバイスありがとうございます。
記事に反映させていただきました。
型注釈は、自分でやったとき変なことをしていたのか、なんかうまくいかなかったのです。
今回はちゃんとできました。
プロフィール

T GYOUTEN

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

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

この人とブロともになる

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