如何使用F#从列表中选择随机值

时间:2015-10-23 22:22:25

标签: random f# f#-interactive

我是F#的新手,我正在试图弄清楚如何从列表/字符串数组中返回一个随机字符串值。

我有一个这样的清单:

["win8FF40", "win10Chrome45", "win7IE11"]

如何从上面的列表中随机选择并返回一个项目?

这是我的第一次尝试:

let combos = ["win8FF40";"win10Chrome45";"win7IE11"]  

let getrandomitem () =  
  let rnd = System.Random()  
  fun (combos : string[]) -> combos.[rnd.Next(combos.Length)]  

3 个答案:

答案 0 :(得分:13)

此处由 latkin mydogisbox 给出的答案都很好,但我仍然想添加有时使用的第三种方法。这种方法并不快,但它更灵活,更易于组合,并且对于小序列来说足够快。根据您的需要,您可以使用此处给出的更高性能选项之一,或者您可以使用以下选项。

使用Random

的单参数函数

我经常定义一个shuffleR函数,而不是直接让您选择单个元素:

open System

let shuffleR (r : Random) xs = xs |> Seq.sortBy (fun _ -> r.Next())

此函数的类型为System.Random -> seq<'a> -> seq<'a>,因此它适用于任何类型的序列:列表,数组,集合和延迟评估的序列(尽管不具有无限序列)。

如果您想要列表中的单个随机元素,您仍然可以这样做:

> [1..100] |> shuffleR (Random ()) |> Seq.head;;
val it : int = 85

但你也可以拿三个随机挑选的元素:

> [1..100] |> shuffleR (Random ()) |> Seq.take 3;;
val it : seq<int> = seq [95; 92; 12]

无参数函数

有时,我并不关心必须传递Random值,所以我改为定义这个替代版本:

let shuffleG xs = xs |> Seq.sortBy (fun _ -> Guid.NewGuid())

它的工作方式相同:

> [1..100] |> shuffleG |> Seq.head;;
val it : int = 11
> [1..100] |> shuffleG |> Seq.take 3;;
val it : seq<int> = seq [69; 61; 42]

虽然Guid.NewGuid()的目的并不是提供随机数,但对于我的目的而言,它通常是随机的 - 随机的,在不可预测的的意义上。

广义函数

shuffleRshuffleG都不是真正随机的。由于RandomGuid.NewGuid()的工作方式,这两个函数都可能导致分布略有偏差。如果这是一个问题,您可以定义更通用的shuffle函数:

let shuffle next xs = xs |> Seq.sortBy (fun _ -> next())

此函数的类型为(unit -> 'a) -> seq<'b> -> seq<'b> when 'a : comparison。它仍然可以与Random

一起使用
> let r = Random();;    
val r : Random    
> [1..100] |> shuffle (fun _ -> r.Next()) |> Seq.take 3;;
val it : seq<int> = seq [68; 99; 54]
> [1..100] |> shuffle (fun _ -> r.Next()) |> Seq.take 3;;
val it : seq<int> = seq [99; 63; 11]

但您也可以将它与基类库提供的一些加密安全随机数生成器一起使用:

open System.Security.Cryptography
open System.Collections.Generic

let rng = new RNGCryptoServiceProvider ()
let bytes = Array.zeroCreate<byte> 100
rng.GetBytes bytes

let q = bytes |> Queue

FSI:

> [1..100] |> shuffle (fun _ -> q.Dequeue()) |> Seq.take 3;;
val it : seq<int> = seq [74; 82; 61]

不幸的是,正如您从此代码中看到的那样,它非常麻烦和脆弱。你必须预先知道序列的长度; RNGCryptoServiceProvider实施IDisposable,因此您应确保在使用后处置rng;并且项目将在使用后从q移除,这意味着它不可重复使用。

密码随机排序或选择

相反,如果确实需要加密正确的排序或选择,那么这样做会更容易:

let shuffleCrypto xs =
    let a = xs |> Seq.toArray

    use rng = new RNGCryptoServiceProvider ()
    let bytes = Array.zeroCreate a.Length
    rng.GetBytes bytes

    Array.zip bytes a |> Array.sortBy fst |> Array.map snd

用法:

> [1..100] |> shuffleCrypto |> Array.head;;
val it : int = 37
> [1..100] |> shuffleCrypto |> Array.take 3;;
val it : int [] = [|35; 67; 36|]

但这并不是我曾经做过的事情,但我认为为了完整起见我会把它包含在这里。虽然我没有测量它,但它很可能不是最快的实现,但它应该是加密随机的。

答案 1 :(得分:8)

您的问题是,您正在混合package pack1; parameter from_pack = 1; typedef struct packed { logic [7:0] test1; logic test2; } test_t; function automatic void hello(); $display("in pack1"); endfunction endpackage package pack2; parameter from_pack = 2; typedef struct packed { logic [7:0] test1; logic test2; logic [18:0] test3; } test_t; function automatic void hello(); $display("in pack2"); endfunction endpackage module top(); parameter PACKAGE_INST = 1; generate case (PACKAGE_INST) 1: begin // import pack1::hello; import pack1::*; end default: begin // import pack2::hello; import pack2::*; end endcase // Error! Compiler doesn't know anything about the test_t type test_t test_struct; // pack1::test_t test_struct; initial begin $display("P1 = %d", P1); // Error at elaboration! Simulator cannot find the 'hello()' function hello(); // pack1::hello(); end endgenerate endmodule 和F#Array s(List*type*[]的类型表示法)。您可以像这样修改它以使用列表:

Array

话虽如此,索引到let getrandomitem () = let rnd = System.Random() fun (combos : string list) -> List.nth combos (rnd.Next(combos.Length)) 通常是一个坏主意,因为它具有O(n)性能,因为F#列表基本上是链表。如果可能的话,最好将List放入数组:

combos

答案 2 :(得分:7)

我刚刚写了一篇关于这个主题的博文:http://latkin.org/blog/2013/11/16/selecting-a-random-element-from-a-linked-list-3-approaches-in-f/

在那里给出了3种方法,讨论了每种方法的性能和权衡。

总结:

// pro: simple, fast in practice
// con: 2-pass (once to get length, once to select nth element)
let method1 lst (rng : Random)  =
    List.nth lst (rng.Next(List.length lst))

// pro: ~1 pass, list length is not bound by int32
// con: more complex, slower in practice
let method2 lst (rng : Random) =
    let rec step remaining picks top =
        match (remaining, picks) with
        | ([], []) -> failwith "Don't pass empty list"
        //  if only 1 element is picked, this is the result
        | ([], [p]) -> p
        // if multiple elements are picked, select randomly from them
        | ([], ps) -> step ps [] -1
        | (h :: t, ps) ->
            match rng.Next() with
            // if RNG makes new top number, picks list is reset
            | n when n > top -> step t [h] n
            // if RNG ties top number, add current element to picks list
            | n when n = top -> step t (h::ps) top
            // otherwise ignore and move to next element
            | _ -> step t ps top
    step lst [] -1

// pro: exactly 1 pass
// con: more complex, slowest in practice due to tuple allocations
let method3 lst (rng : Random) =
    snd <| List.fold (fun (i, pick) elem ->
               if rng.Next(i) = 0 then (i + 1, elem)
               else (i + 1, pick)
           ) (0, List.head lst) lst

编辑我应该澄清,上面显示了一些从列表中获取随机元素的方法,假设您必须使用列表。如果它符合程序设计的其余部分,那么从数组中获取随机元素肯定更有效。