スポンサーサイト

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

F#入門第50回(seq (3))

今回のお題は「seq (3)」です
 
今回はseq絡みの関数をいくつか紹介したいとおもいます。
 
まずはSeq.cacheからです。次をみてください。
 
> let fiboSeq =
    let rec nextFibo (n1,n2) =
       seq{ printf "*"
            yield n1+n2
            yield! nextFibo(n2,n1+n2)} 
    nextFibo(0,1) ;;         
 
val fibo : seq<int>
    
これはフィボナッチ数列を与えているのですが、最初のいくつかの項を取ってきてみます。
まずは一つだけ。


> Seq.take 1 fiboSeq;;
*val it : seq<int> = seq [1]
 
ここで*がyield n1+n2が実行された回数です。 
 
次に二つ。

> Seq.take 2 fiboSeq;;
**val it : seq<int> = seq [1; 2]
 
これらからわかるのは、毎回最初から計算しなおしているということです。
ここでSeq.cacheを利用すると、計算しているものを記録しておいて、残りの必要分だけ計算して返してくれます。
次のようにします。
 
> let fiboSeq =
    let rec nextFibo (n1,n2) =
       seq{ printf "*"
            yield n1+n2
            yield! nextFibo(n2,n1+n2)} |> Seq.cache
    nextFibo(0,1)  ;;
 
val fiboSeq : seq<int>
 
実行例
> Seq.take 1 fiboSeq;;
*val it : seq<int> = seq [1]
 
> Seq.take 2 fiboSeq;;
*val it : seq<int> = seq [1; 2] //計算回数が減っている
 
次に、SeqにはSeq.groupBy等のSQLチックな関数を紹介します。
 
まずサンプル例のためrecord型を二つとそれぞれのsequenceを定義しておきます。
 
type People =
    { p_index:int;
      name:string;
      height:float;
      pref_index :int}
                 
type Prefecture =
    {index : int;
     name: string}
     
let peoples = seq[ {p_index = 1;name = "John";height = 172.0;pref_index = 3};
                   {p_index = 2;name = "Ben";height = 192.0;pref_index = 1};
                   {p_index = 3;name = "Tom";height = 151.0;pref_index = 1};
                   {p_index = 4;name = "Paul";height = 174.0;pref_index = 2};
                   {p_index = 5;name = "Kate";height = 163.0;pref_index = 3}]
                       
let prefectures = seq[{index = 1;name = "Aomori"};
                      {index = 2;name = "Iwate"}; 
                      {index = 3;name = "Saga"}] 
                      
まずはSeq.sortByを使って並べ替えてみます。
 
let q = peoples 
        |> Seq.sortBy (fun p -> p.height)
 
表示用部分だけ文字列として抜き出します。
 
let q = peoples 
        |> Seq.sortBy (fun p -> p.height)
        |> Seq.map (fun p -> sprintf "name = %s height = %f" p.name p.height)
        
実行例
 
> q;;
val it : seq<string> =
  seq
    ["name = Tom height = 151.000000"; "name = Kate height = 163.000000";
     "name = John height = 172.000000"; "name = Paul height = 174.000000"; ...]
 
次にgruopByを使ってみます
 
let q = peoples 
        |> Seq.groupBy (fun p -> p.pref_index)
 
 
q;;
val it : seq<int * seq<People>> =
  seq
    [(3, seq [{p_index = 1;
               name = "John";
               height = 172.0;
               pref_index = 3;}; 
               {p_index = 5;
                name = "Kate";
                height = 163.0;
                pref_index = 3;}]);
     (1, seq [{p_index = 2;
               name = "Ben";
               height = 192.0;
               pref_index = 1;};
               {p_index = 3;
                name = "Tom";
                height = 151.0;
                pref_index = 1;}]);
     (2, seq [{p_index = 4;
               name = "Paul";
               height = 174.0;
               pref_index = 2;}])]
 
ちょっと複雑になりますが、pref_index毎のheightの和を求めてみます。
 
let u = peoples 
        |> Seq.groupBy (fun p -> p.pref_index)
        |> Seq.map (fun (index,sequ) -> sprintf "%d : %f" index (Seq.sumBy(fun p2 -> p2.height) sequ) )
        
> u;;
val it : seq<string> =
  seq ["3 : 335.000000"; "1 : 343.000000"; "2 : 174.000000"]
  
次にpeoplesとprefecturesをpeoplesのpref_indexとprefecturesのindexをキーとして結び付けてみたいとおもいます。
 

let u = seq{for p in peoples do
                for pre in prefectures do
                    if p.pref_index = pre.index then
                        yield sprintf "%s is from %s" p.name pre.name }
                        
                        
実行例
> u;;
val it : seq<string> =
  seq
    ["John is from Saga"; "Ben is from Aomori"; "Tom is from Aomori";
     "Paul is from Iwate"; ...] 
     

スポンサーサイト

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

F#入門第49回(seq (2))

今回のお題は「seq (2)」です。
 
まずは前回の宿題の答えから
宿題
let nums = Seq.unfold (fun num ->
    if (num < 2 || (2 < num && num < 10) ) then Some(num.ToString(), num + 1) else None) 0
で出来上がるnumsはどんなものでしょうか。
(解説)
if の中身に留意してください。numが0,1,2と増えていくと2でNoneとなります。これが、追加を打ち切るサインですから、結局numが0,1に対するnum.ToString()の値だけがnumsの要素となるので答えは
seq ["0";"1"] となります。
 
さて、今回の話題はsequence expressionsです。
以前リスト内含表記というのを少しだけ紹介しましたが、あれはsequence expressionsのlistへの適用です。
それでは例をみてください。
 
> let k =
    seq{ for i in 1 .. 3 do
            printfn "now yield"  
            yield 2*i
        };;
 
val k : seq<int>
 
> k;;
now yield
now yield
now yield
val it : seq<int> = seq [2; 4; 6]
 
seq{ と }の間に通常のF#のコードと同様にコードを書くとyieldの部分で、seqに要素を押しこんで返してくれるというのがsequence expressionsです。
(mutableな値は使用できない(ref型は使えます)、内部で型は定義できないという制約はあります。)
 
ここで復習がてらリスト内含表記を一つ取り上げます。
 
> let u =
    [   for i in 1 ..3 do
            printfn "now  yield"
            yield 2*i
     ];;
 
now  yield
now  yield
now  yield
 
val u : int list = [2; 4; 6]
 
> u;;
val it : int list = [2; 4; 6]
 
ほぼ先ほどの例と同じですが、ちょっと違うところがあります。どこでしょうか。
そうです、リストでは定義したらすぐnow  yieldが3回続き、リストの内容まで表示されましたが、seqでは、定義しただけでは、now yieldは表示されず、sequenceの内容も表示されませんでした。k;;としたところで初めてnow yieldが表示されました。
実はsequenceは定義してすぐ計算されるのではなく、必要とされるときに、必要とされる部分までが計算されるのです。このような仕組みをlazy evaluation(遅延処理(評価))と言います
 
例えばSeq.take関数はsequenceから最初の何項かをとってきて、新しいsequenceを返す関数です。次の例では必要となる部分だけ計算されていることが分かると思います。
 
> Seq.take 1 k ;;
now yield
val it : seq<int> = seq [2]
 
> Seq.take 2 k ;;
now yield
now yield
val it : seq<int> = seq [2; 4]
 
このことを利用すると(見た目)無限数列が定義できます。次の例をみてください。
let examp =
    seq{    
            let rnd = new System.Random ()
            while true do
                yield rnd.Next(100)
            
        }
 
Randomは乱数を発生するクラスで、インスタンスrndを作成しrnd.Next(100)で0から99までの間の乱数を一つ返します。
上の例では無限ループになっていますが、実際に計算されるのは、必要となる分だけですから、無限ループにおちいることはありません。
例えば次のように使います。
 
> Seq.take 10 examp;;
val it : seq<int> = seq [36; 76; 71; 16; ...]
 
必要な分だけ必要な時にとってこれるというわけです。
 
yieldは普通のF#でコード部分では使えませんでしが、他にも普通のF#のコード部分では使えない、特別なキーワードyield!(yield bangと読みます。)があるのでこれを紹介しておきます。これはsequenceやlistやarray等のINumerable<>型のものに対して使うことができ、中身を取り出して要素にしてくれます。
分かりにくいと思いますので例を挙げます。
 
> let examp =
    seq{    yield! seq[1]
            yield! [2;3]
            yield! [|4|]
        };;
 
val examp : seq<int>
 
> examp;;
val it : seq<int> = seq [1; 2; 3; 4]
 
このyield!は再帰関数と組み合わせて、ツリー構造をフラットにするときなどに、よく使用されます。
 
簡単な例として乱数の列をyield!と再帰関数を使って作成してみます。
 
let examp =
    let rnd = new System.Random ()
    let rec Rnd =
        seq{ yield rnd.Next(100)
             yield! Rnd}    
    Rnd 
 
> Seq.take 10 examp;;
val it : seq<int> = seq [17; 75; 28; 6; ...]
 
一般にlistやarray等のINumerable<T>を実装しているものをF#では#seq<T>で表します。
例えばSeq.Concatという関数があります。これは
seq< #seq<'a> > -> seq<'a> 型として定義されています。
例えば次のように使います。
 
> let u = seq[[2];[3;4];[5]];;
val u : seq<int list> = [[2]; [3; 4]; [5]]
 
> let v = Seq.concat u;;
val v : seq<int> //遅延処理なので、まだ処理されていない
 
> v;;
val it : seq<int> = seq [2; 3; 4; 5] //中身だけが連結されてでてくる
 
#seq型に対して使えるので
let u = seq[[|2|];[|3;4|];[|5|]] 
としても
let u = seq[seq[2];seq[3;4];seq[5]] 
としても同様の操作が可能です。

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

F#入門第48回(seq (1))

今回のお題は「seq (1)」です。
 
seqというのは、seqence(数列)の頭文字で、同一の型のものが並んでいるものです。この型をTとすると,seq<T>と表されます。
seq<T>型は.NETでいうところのIEnumerable<T>というインターフェイスの別名です。(enumerableというのは日本語では、可算で「一つ二つと数えられる」ということです。

まずはルールを指定してsequenceを作成してみましょう。
ここで、よく使われるのがSeq.unfold関数です。この関数は一般性がある分、使い方がややこしいと思うので少し説明したいと思います。
まずsequenceを作成していくのですが、直接、項を入れていくのではなくて、ワンクッションおいて、項を作り出しては入れていきます。まずstate(状態)というのを考えます。
そして次々にあるルールで状態が変わっていくことを考えます。
次の図を見てください.
 
状態0 ⇒最初の項

状態1⇒次の項

状態2⇒その次の項

 
つまり、「状態から、次の状態へ移るルールと状態からseqeunceに入れる値を決めるルール」があれば、あとは最初の状態0の値があればsequenceは一意的に作ることができるわけです。
ここで、「状態から、次の状態へ移るルールと状態からseqeunceに入れる値を決めるルール」を一つの関数で表します。
fun 状態 -> (seqeunceに入れる値,次の状態) という形です。
 
例1
状態 sequenceの項
1  ⇒ 2

2  ⇒ 4

3  ⇒ 6
 
ならば、関数は fun n -> ( 2*n , n+1 ) 初期値は1
 
例2
状態 sequenceの項
1  ⇒ "1"

2  ⇒ "2"

3  ⇒ "3"
 
ならば、関数は fun n -> ( n.ToString() , n+1 ) 初期値は1
 
例3
状態 sequenceの項
(1,1)  ⇒ 1

(1,2)  ⇒ 1

(2,3)  ⇒ 2

(3,5)  ⇒ 3

(5,8)  ⇒ 5
 
ならば、関数は fun (n1,n2) -> ( n1 ,(n2 , n1 + n2) ) 初期値は(1,1)となります。
 
なお、返り値をoption型にしてやるとSome(a)のときはsuquenceに追加され、Noneが返されるとそこで追加が停止しそれ以降は追加されません。
 
それでは上の例について、追加停止条件を適当に設定して実行してみます。
 
例1
 
let ex01 = Seq.unfold 
                (fun n ->
                    if (n < 4 ) then Some(2 * n, n + 1) 
                    else None) 
                1
 
> ex01;;
val it : seq<int> = seq [2; 4; 6]
 
例2
 
let ex02 = Seq.unfold 
                (fun n ->
                    if (n < 8 ) then Some(n.ToString() , n + 1) 
                    else None) 
                1
 
> ex02;;
val it : seq<string> = seq ["1"; "2"; "3"; "4"; ...]
 
例3
 
let ex03 = Seq.unfold 
                (fun (n1,n2) ->
                    if (n1 < 7 ) then Some(n1,(n2,n1+n2)) 
                    else None) 
                (1,1)
                
 
> ex03;;
val it : seq<int> = seq [1; 1; 2; 3; ...]
 
(例3はいわゆるフィボナッチ数列のseqを作成しています。)
 
listやarrayにはunfold関数がないのですが、Seq.to_listやSeq.to_array関数で簡単にSeq<T>型から、list<T>,array<T>型に変換できます。
 
 
宿題
let nums = Seq.unfold (fun num ->
    if (num < 2 || (2 < num && num < 10) ) then Some(num.ToString(), num + 1) else None) 0
で出来上がるnumsはどんなものでしょうか。

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

F#入門第47回(構造体)

今回のお題は「構造体」です。
 
構造体は特殊なクラスの一種で、いわば一般のクラスの軽量化版です。どんなところが特殊かというと、主に次の点です。
(1)スタックに値がおかれる。
(2)全部のフィールドがすべて値として等しいとき等しいとされる
(3)継承できない
 
まず、構造体の定義の仕方ですが、普通のクラスを定義するのと同様にしてください。
ただ、typeの前に[<Struct>]をつけるか、body部分をstruct endで囲んでください。

[<Struct>]
type Vec2D (x : float,y:float) =
    member this.ShowContents ()
                = printfn "x = %f y = %f" x y
または
 
type Vec2D (x : float,y:float) =
  struct
    member this.ShowContents ()
                = printfn "x = %f y = %f" x y
  end
  
上の2例では暗黙的定義方法を用いています。(インスタントフィールドx,yが自動生成されています。)
次に明示的定義方法の例をあげてみます。
type Vec2D  =
  struct
    val _x :float
    val _y :float
    
    new (x,y) = {_x = x ; _y = y}
    
    member this.ShowContents ()
                = printfn "x = %f y = %f" this._x this._y
  end 
 
実行例
> let t = new Vec2D (1.0,2.0);;
 
val t : Vec2D = FSI_0007+Vec2D
 
> t;;
x = 1.000000 y = 2.000000
val it : Vec2D = FSI_0007+Vec2D {ShowContents = null;
                                 _x = 1.0;
                                 _y = 2.0;}
 
> t.ShowContents ();;
x = 1.000000 y = 2.000000
val it : unit = ()
 
 
さて、フィールドをmutableにしてみます。
 
type Vec2D  =
  struct
    [<DefaultValue>]
    val mutable _x :float
    [<DefaultValue>]
    val mutable _y :float
    
    member this.ShowContents ()
                = printfn "x = %f y = %f" this._x this._y
  end      
  
コンストラクターを定義していませんが、クラスとは異なり、構造体はデフォルトのコンストラクタを自動的に定義してくれて、これによって各フィールドはゼロかnullに初期化されます。
 
 
使ってみます
 
 > let t = Vec2D ();;
val t : Vec2D = FSI_0002+Vec2D
 
> t.ShowContents() ;;
x = 0.000000 y = 0.000000
val it : unit = () 
 
> t._x <- 5.0;;
  t._x <- 5.0;; 
  error FS0191: A value must be local and mutable in order to mutate the contents of a value type, e.g. 'let mutable x = ...'
とここでエラーです。
エラーメッセージにもあるように、識別子自体もmutable指定していないと、フィールドの値の変更ができません。よって次のようにします。
 
> let mutable u = Vec2D ();;
val mutable u : Vec2D = FSI_0002+Vec2D
 
> u._x <- 5.0;;
val it : unit = ()
 
> u.ShowContents();;
x = 5.000000 y = 0.000000
val it : unit = ()
                         

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

F#入門第46回(列挙型)

今回のお題は「列挙型」です。
 
まずは宿題の解答から
(宿題)
string型(System.String)に、その文字の長さを表示するメソッドMyPrintLengthを追加してみてください。
たとえば "world".MyPrintLenght()で「文字数は5文字です」と表示するようにしてください。
 
(解答例)
 
type System.String with
    member this.MyPrintLength () =
        printfn "文字数は%d文字です" this.Length
 
(実行例)
 
> "world".MyPrintLength() ;;
文字数は5文字です
val it : unit = ()
 
列挙型(enumeration)は識別子のグループとprimitiveな整数型(sbyte byte int16 int32 uint64 uint16 uint32 uint64 char)(どれか一種類)を対応づけたものです。
これは、Discriminated Unionのように一つの型として利用できます。
例えば
 
type Rank =
    |A = 100
    |B = 70
    |C = 50
 
としておくと
 
> Rank.A = Rank.B;;
val it : bool = false
 
let judge x =
    if x > 1000.0 then Rank.A
    elif x > 100.0 then Rank.B
    else Rank.C
    
val judge : float -> Rank 
 
> judge 123.5;;
val it : Rank = B
 
列挙型はDiscriminated Unionより軽量です。一方列挙型は、それぞれ一種類のprimitiveな整数型しか、保持することができません。
実質的に列挙型は、整数型の見え方を変えたものですので、エラーが紛れ込む可能性も高くなります。例えば次のような意味のない演算も可能です。
> Rank.A + Rank.B;;
val it : Rank = 170
 
列挙型から整数型への変換は次のように行います。
> int Rank.C;;
val it : int = 50
 
整数型から列挙型への変換は次のように行います。
> enum<Rank> 50 ;;
val it : Rank = C
 
存在しない値でもエラーはでませんから、注意が必要です。
> enum<Rank> 20000 ;;
val it : Rank = 20000
 
ということで、ケースバイケースでDiscriminated Unionと使い分ける必要があるかと思います。

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

プロフィール

T GYOUTEN

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

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

この人とブロともになる

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