我是功能世界的新手,并感谢对此的帮助。

我想从这个简单的函数中取代丑陋的命令式代码,但不知道该怎么做。

我想要的是根据概率值从 IEnumerable(F# 中的 seq)中随机选择一些元素 - 元组中的第二个项目(因此“概率”为 0.7 的项目将比 0.1 的项目更频繁地被选择)。

/// seq<string * float>
let probabilitySeq = seq [ ("a", 0.7); ("b", 0.6); ("c", 0.5); ("d", 0.1) ]

/// seq<'a * float> -> 'a
let randomPick probSeq =
    let sum = Seq.fold (fun s dir -> s + snd dir) 0.0 probSeq
    let random = (new Random()).NextDouble() * sum
    // vvvvvv UGLY vvvvvv
    let mutable count = random
    let mutable ret = fst (Seq.hd probSeq )
    let mutable found = false
    for item in probSeq  do
        count <- count - snd item
        if (not found && (count < 0.0)) then
            ret <- fst item  //return ret;  //in C#
            found <- true
    // ^^^^^^ UGLY ^^^^^^
    ret

////////// at FSI: //////////

> randomPick probabilitySeq;;
    val it : string = "a"
> randomPick probabilitySeq;;
    val it : string = "c"
> randomPick probabilitySeq;;
    val it : string = "a"
> randomPick probabilitySeq;;
    val it : string = "b"

我认为 randomPick 的命令式实现非常简单,但在功能上呢?

这是功能性的,但采用 列表 而不是 seq (想要)。
//('a * float) list -> 'a
let randomPick probList =
    let sum = Seq.fold (fun s dir -> s + snd dir) 0.0 probList
    let random = (new Random()).NextDouble() * sum
    let rec pick_aux p list =
        match p, list with
        | gt, h::t when gt >= snd h -> pick_aux (p - snd h) t
        | lt, h::t when lt < snd h -> fst h
        | _, _ -> failwith "Some error"
    pick_aux random probList

最佳答案

使用 Matajon 建议的原理的 F# 解决方案:

let randomPick probList =
    let ps = Seq.skip 1 (Seq.scan (+) 0.0 (Seq.map snd probList))
    let random = (new Random()).NextDouble() * (Seq.fold (fun acc e -> e) 0.0 ps)
    Seq.find (fun (p, e) -> p >= random)
             (Seq.zip ps (Seq.map fst probList))
    |> snd

但是在这种情况下我可能也会使用基于列表的方法,因为无论如何都需要预先计算概率值的总和......

关于f# - 如何从函数中删除命令式代码?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/1586188/

10-11 12:27