需要帮助优化我的代码f#(创建一个元组数组)

时间:2014-08-26 21:35:23

标签: list optimization recursion f# tuples

我需要帮助优化这段代码,需要12秒钟,我需要大约4秒钟。

let publication idx (lst: string [] list) = // returns a specific value of the string [] list
    lst
    |>Array.map (fun arr -> arr.[idx])


let rec Tuple (x:int) (ID:string list) (Information:string [] list) =
 if x < ID.Length then  
      let muro =  [|(ID.[x], Information|> List.filter (fun elem -> elem.[1] = humanID.[x] )|> publication 0   |> List.toArray )|]
      let rest = Tuple (x+1) ID Information
      Array.append muro rest     
 else  [||] 
let FinalTuple= Tuple 0 ID Information

finalTuple是:(string*string []) []

递归需要很长时间才能完成,我似乎无法让它更快(ID.Length是1600)

感谢您的帮助

2 个答案:

答案 0 :(得分:3)

在尝试提高一段代码的性能时,首先要做的是消除不必要的内存分配。一些地方你可以改善这个:

  1. 在数组上进行映射比在列表上进行映射要快,但不是 比转换为数组,映射然后转换回来更快 列表。

  2. 避免使用Array.append。这导致每次都进行新的内存分配。相反,您可以考虑ResizeArray或列表,其中附加费用要便宜得多。

  3. 尝试使函数尾递归。在您的情况下,这意味着您需要将中间结果传递到下一级递归。在你的情况下(如果我理解正确),这意味着将muro传递给调用链,然后将该调用的结果添加到muro,然后再将其传递给下一个递归。

  4. 尾递归函数看起来像这样:

    let rec Tuple (x:int) (ID:string list) (Information:string [] list) (result:*correct type here*) =
        if x < ID.Length then  
            let muro =  [|(ID.[x], Information|> List.filter (fun elem -> elem.[1] = humanID.[x] )|> publication 0   |> List.toArray )|]
            Tuple (x+1) ID Information Array.append muro rest     
        else  result
    let FinalTuple= Tuple 0 ID Information [||]
    

    这可能会改变结果数组的顺序,因此根据您的需要,您可能需要稍微修改一下。

答案 1 :(得分:3)

我同意@ mydogisbox的一般观点 - 从阵列到列表的来回往返不会对性能有很大帮助。
话虽这么说,我遇到的第一个问题是,我不得不挖掘一下才弄清楚代码在做什么 - 所以我冒昧地做了第一次重写,只是为了看看我是否理解发生了什么:< / p>

let extract1 (ids:string[]) (info:string[][]) =
    [|
        for id in ids ->
            (id, [| for record in info do if record.[1] = id then yield record.[0] |])
    |]

我感觉,阅读你的代码是这样的:给定一个“记录”数组 - 包含字段0中的感兴趣的东西(可能是出版物?)和字段1中的作者的数组,目标是给定的一组作者ID,提取他们的出版物。或类似的东西。

现在这不是很漂亮。此外,我不知道这是否有用,所以让我们做一个基准测试 - 一个1,000,000条记录的数据集,看起来像我认为的那样:

let ids = [| 1 .. 100 |] |> Array.map string 
let rng = System.Random()
let dataset = 
    [| for i in 0 .. 1000000 -> 
        [| System.Guid.NewGuid() |> string; rng.Next(0,100) |> string |] |]

在FSI中运行它给我:

> extract1 ids dataset |> ignore;;
Real: 00:00:01.486, CPU: 00:00:01.484, GC gen0: 3, gen1: 3, gen2: 0
val it : unit = ()

我们可以让它更漂亮,或更实用吗?我们试试吧:

let extract2 (ids:string[]) (info:string[][]) =
    ids 
    |> Seq.map (fun id -> 
        id,
        info 
        |> Seq.filter (fun record -> record.[1] = id)
        |> Seq.map (fun record -> record.[0])
        |> Seq.toArray)
    |> Seq.toArray

判决结果?

> extract2 ids dataset |> ignore;;
Real: 00:00:01.588, CPU: 00:00:01.593, GC gen0: 3, gen1: 3, gen2: 0
val it : unit = ()

更漂亮,但不是更好。也许问题是我们正在进行多次传递,每个传递一次。听起来好像我们应该分组?

let extract3 (ids:string[]) (info:string[][]) =
    let IDs = ids |> Set.ofArray
    info
    |> Seq.groupBy (fun row -> row.[1])
    |> Seq.filter (fun (id,rows) -> IDs |> Set.contains id)
    |> Seq.map (fun (id,rows) -> id, rows |> Seq.toArray)
    |> Seq.toArray

> extract3 ids dataset |> ignore;;
Real: 00:00:00.387, CPU: 00:00:00.390, GC gen0: 8, gen1: 8, gen2: 0
val it : unit = ()

现在我们正在谈论。我相信我们可以挤得更多 - 随意去做吧。但重点是代码也更简单(IMO),并且更清楚地表达了意图。希望这有帮助!