6

私はまだF#のことを理解することに取り組んでいます-私が知っている他の言語から単に翻訳するのではなく、F#で「考える」方法を考え出そうとしています。

私は最近、前後の間に1:1のマップがない場合について考えています。List.mapが失敗する場合。

この一例は移動平均です。通常、n個のアイテムを平均すると、長さlenのリストに対してlen-n+1の結果が得られます。

そこにいる教祖にとって、これはそれを行うための良い方法ですか(ジョモフィッシャーからつままれたキューを使用して)?

//Immutable queue, with added Length member
type Fifo<'a> =
    new()={xs=[];rxs=[]}
    new(xs,rxs)={xs=xs;rxs=rxs}

    val xs: 'a list;
    val rxs: 'a list;

    static member Empty() = new Fifo<'a>()
    member q.IsEmpty = (q.xs = []) && (q.rxs = [])
    member q.Enqueue(x) = Fifo(q.xs,x::q.rxs)
    member q.Length() = (List.length q.xs) + (List.length q.rxs)
    member q.Take() =
        if q.IsEmpty then failwith "fifo.Take: empty queue"
        else match q.xs with
                | [] -> (Fifo(List.rev q.rxs,[])).Take()
                | y::ys -> (Fifo(ys, q.rxs)),y

//List module, add function to split one list into two parts (not safe if n > lst length)
module List =
    let splitat n lst =
        let rec loop acc n lst =
            if List.length acc = n then
                (List.rev acc, lst)
            else
                loop (List.hd lst :: acc) n (List.tl lst)
        loop [] n lst

//Return list with moving average accross len elements of lst
let MovingAverage (len:int) (lst:float list) = 
    //ugly mean - including this in Fifo kills genericity
    let qMean (q:Fifo<float>) = ((List.sum q.xs) + (List.sum q.rxs))/(float (q.Length()))

    //get first part of list to initialise queue
    let (init, rest) = List.splitat len lst

    //initialise queue with first n items
    let q = new Fifo<float>([], init)

    //loop through input list, use fifo to push/pull values as they come
    let rec loop (acc:float list) ls (q:Fifo<float>) =
        match ls with
        | [] -> List.rev acc
        | h::t -> 
            let nq = q.Enqueue(h) //enqueue new value
            let (nq, _) = nq.Take() //drop old value
            loop ((qMean nq)::acc) t nq //tail recursion

    loop [qMean q] rest q

//Example usage    
MovingAverage 3 [1.;1.;1.;1.;1.;2.;2.;2.;2.;2.]

(おそらく、Fifoから継承してMovingAverageQueueを実装するのがより良い方法でしょうか?)

4

7 に答える 7

7

パフォーマンスをあまり気にしない場合は、次の非常に簡単な解決策があります。

#light

let MovingAverage n s =
   Seq.windowed n s
   |> Seq.map Array.average

let avgs = MovingAverage 5000 (Seq.map float [|1..999999|])

for avg in avgs do
    printfn "%f" avg
    System.Console.ReadKey() |> ignore

これにより、各「ウィンドウ」の平均が最初から再計算されるため、ウィンドウが大きい場合は不十分です。

いずれにせよ、Seq.windowedをチェックしてください:

http://research.microsoft.com/projects/cambridge/fsharp/manual/FSharp.Core/Microsoft.FSharp.Collections.Seq.html

そんなことをバックポケットに入れておくと便利です。

于 2008-11-18T03:26:09.750 に答える
4

パフォーマンスに関心がある場合、次のようなものを使用して移動平均を効率的に計算できます(3日間のウィンドウで移動平均を計算していると仮定します)

数値[n]現在の合計[n]
--------- ---------------
n [0] = 77=数値[0]
n [1] = 1 8 = RunningTotal [1-1] + Numbers [1]
n [2] = 2 10 = RunningTotal [2-1] + Numbers [2]
n [3] = 8 11 = RunningTotal [3-1] + Numbers [3]-Numbers [3-3]
n [4] = 4 14 = RunningTotal [4-1] + Numbers [4]-Numbers [4-3]
n [5] = 1 13 = RunningTotal [5-1] + Numbers [5]-Numbers [5-3]
n [6] = 9 14 = RunningTotal [6-1] + Numbers [6]-Numbers [6-3]
..。
N RunningTotal [N] = RunningTotal [N-1] + Numbers [N]-Numbers [N-3]

これについての難しい部分は、以前の現在の合計と数値Nウィンドウを保持することです。私は次のコードを思いついた:

let movingAverage days l =
    seq {
        let queue = new Queue<_>(days : int)
        let divisor = decimal days

        let total = ref 0m
        for cur in l do
            queue.Enqueue(cur)
            total := !total + cur
            if queue.Count < days then
                yield (cur, 0m)
            else
                yield (cur, !total / divisor)
                total := !total - (queue.Dequeue())
    }

このバージョンはHaskellコードほど見栄えが良くありませんが、実行ごとに「ウィンドウ」を再計算することに関連するパフォーマンスの問題を回避する必要があります。累計を保持し、以前に使用した番号をキューに保持するため、非常に高速である必要があります。

楽しみのために、私は簡単なベンチマークを書きました:

#light
open System
open System.Collections.Generic
open System.Diagnostics;

let windowAverage days (l : #seq<decimal>) = Seq.windowed days l |> Seq.map (Seq.average)

let princessAverage days l =
    seq {
        let queue = new Queue<_>(days : int)
        let divisor = decimal days

        let total = ref 0m
        for cur in l do
            queue.Enqueue(cur)
            total := !total + cur
            if queue.Count < days then
                yield (cur, 0m)
            else
                yield (cur, !total / divisor)
                total := !total - (queue.Dequeue())
    }

let testData =
    let rnd = new System.Random()
    seq { for a in 1 .. 1000000 -> decimal (rnd.Next(1000)) }

let benchmark msg f iterations =
    let rec loop = function
        | 0 -> ()
        | n -> f 3 testData |> ignore; loop (n - 1)

    let stopWatch = Stopwatch.StartNew()
    loop iterations
    stopWatch.Stop()
    printfn "%s: %f" msg stopWatch.Elapsed.TotalMilliseconds

let _ =
    let iterations = 10000000
    benchmark "princessAverage" princessAverage iterations
    benchmark "windowAverage" windowAverage iterations
    printfn "Done"

結果:

princessAverage:1670.791800
windowAverage:2986.146900

私のバージョンは約1.79倍高速です。

于 2009-02-03T18:19:34.400 に答える
2

パフォーマンスが気になり、エレガントなコードが好きなら、試してみてください

module MovingAverage = 
    let selfZip n l =
        Seq.skip n l |> Seq.zip l 

    let runTotal i z =
        Seq.scan ( fun sum (s, e) -> sum - s + e ) i z

    let average n l:seq<'a> =
        Seq.skip n l
        |> selfZip n
        |> runTotal (l |> Seq.take n |> Seq.sum)
        |> Seq.map ( fun sum -> decimal sum / decimal n ) 

 let ma = MovingAverage.average 2 myseq

FSUnit を使用してテストできます

 let myseq = seq { for i in 0..10 do yield i }

 Seq.nth 0 ma |> should equal 0.5
    Seq.nth 1 ma |> should equal 1.5
    Seq.nth 2 ma |> should equal 2.5
    Seq.nth 3 ma |> should equal 3.5

このアルゴリズムの秘訣は、まず最初の n 個の数値を合計し、次にウィンドウの先頭を追加してウィンドウの末尾を減算することで現在の合計を維持することです。スライディング ウィンドウは、シーケンスに対してセルフ zip を実行することによって実現されますが、zip の 2 番目の引数がウィンドウ サイズだけ進められます。

パイプラインの最後で、現在の合計をウィンドウ サイズで割ります。

スキャンは折り畳みに似ていますが、状態の各バージョンをシーケンスに生成します。

パフォーマンス ヒットを伴う可能性はありますが、さらに洗練された解決策は、シーケンスをゼロ パディングする場合、最初の合計を計算する必要がないことを観察することです。

namespace Utils

module MovingAverage = 
    let selfZip n l =
        Seq.skip n l |> Seq.zip l 

    let rec zeros = 
        seq { yield 0.0; yield! zeros} 

    // Create a running total given
    let runTotal z =
        Seq.scan (fun sum (s,e) -> sum - s + e ) 0.0 z

    let average n l =
        Seq.concat [(Seq.take n zeros); l]
        |> selfZip n
        |> runTotal
        |> Seq.map ( fun sum -> sum / float n ) 
        |> Seq.skip n

2 つのシーケンスのラップに関連する 2 番目の間接参照により、パフォーマンスが低下する可能性がありますが、ウィンドウのサイズによっては、おそらくそれほど重要ではありません。

于 2012-08-31T08:06:08.427 に答える
1

これが、ここで提案されているHaskellソリューションの(修正された)F#バージョンです。

編集:末尾再帰で、高速ではありませんが、n = 50000で爆発しません(非末尾再帰バージョンについては編集履歴を参照してください)。

let LimitedAverage n ls = 
    let rec loop acc i n ls = 
        match i with
        | 0 -> acc //i counts down from n to 0, when we hit 0 we stop
        | _ -> match ls with
               | [] -> acc //if we hit empty list before end of n, we stop too
               | x::xs -> (loop (acc + (x / float n)) (i - 1) n xs) //divide this value by n, perform average on 'rest' of list
    loop 0. n n ls

LimitedAverage 50000 (List.map float [1..9999999])
//>val it : float = 25000.5

let rec MovingAverage3 n ls = 
    let rec acc loop i n ls = 
        match i with 
        | 0 -> List.rev acc //i counts down from n to 0, when we hit 0 we stop
        | _ -> match ls with
                | [] -> List.rev acc //if we hit empty list before end of n, we stop too
                | x::xs -> loop (LimitedAverage2 n ls :: acc) (i - 1) n xs // limited average on whole list, then moving average on tail
    loop [] (n + 1) n ls 

MovingAverage3 50000 (List.map float [1..9999999])
//>val it : float list = [25000.5; 25001.5; 25002.5; ...]
于 2008-11-17T13:54:08.173 に答える
0

これは私のバージョンです。

let sma list n =
    let len = List.length list
    let rec loop acc sum cnt =
        if cnt >= len then List.rev acc
        else if cnt < n-1 then loop (0.0::acc) (sum + List.nth list cnt) (cnt+1)
        else loop (((sum + List.nth list cnt)/(float n))::acc) (sum + (List.nth list cnt) - (List.nth list (cnt-n+1))) (cnt+1)
    loop [] 0.0 0

例:

sma (List.map float [5..50]) 5
[0, 0, 0, 0, 7, 8, 9, ...]
于 2015-01-04T22:24:50.633 に答える
-2

私が見る限り、あなたのコードはletステートメントでいっぱいです。私は F# には詳しくありませんが、Haskell をいくつかやりました。機能的パラダイムとは、「どのように」ではなく「何を」考えることを意味します。Fifo だと思いますが、実際には移動平均のセマンティクスを指定する必要があります。

-- the limited average of a list
limitedaverage 0 _ = 0
limited verage n (x:xs) = (x/n) + ( limited average (n-1) xs )

-- a list, transformed into a sequence of moving averages of 
movingaverages n [] = []
movingaverages n (x:xs) = ( movingaverage n (x:xs) : movingaverages n xs )
于 2008-11-17T13:07:18.093 に答える