1

我已经用谷歌搜索并阅读了,并且正在尝试找到一种“正确”的方法来做到这一点,但是我在 SO 上阅读的每个问题似乎都有完全不同的答案。

这是我的问题的要点。文件具有三元组序列的类型签名(a:string,b:string,c:Int64)。作为 f# 的新手,我仍然不能流利地表达类型签名(或者就此而言理解它们)。a 是文件名,b 是内部标识符,c 是表示文件长度(大小)的值。baseconfig 是代码前面的字符串。

ignore(files 
    |> Seq.filter( fun(x,y,z) ->  y = baseconfig)  // used to filter only files we want
    |> Seq.fold( fun f n   -> 
        if( (fun (_,_,z) -> z) n > 50L*1024L*1024L) then
            zipfilex.Add((fun (z:string, _, _) -> z) n)
            printfn("Adding 50mb to zip")
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            ("","",0L)
        else
            zipfilex.Add((fun (z, _, _) -> z) n)
            ("", "", (fun (_, _, z:Int64) -> z) n + (fun (_, _, z:Int64) -> z) f)
    ) ("","",0L)
    )

这段代码应该做的是遍历 中的每个文件files,将其添加到 zip 存档中(但不是真的,它只是在稍后提交的列表中),当文件超过 50MB 时,提交当前待处理的文件到 zip 存档。添加文件很便宜,提交很昂贵,所以我尝试通过批处理来降低成本。

到目前为止,代码有点工作......除了当它接近 150MB 的提交文件时我得到的 ObjectDisposedException。但我不确定这是进行此类操作的正确方法。感觉就像我正在Seq.fold以非常规的方式使用,但是,我不知道有更好的方法来做到这一点。

额外的问题:有没有更好的方法从元组中剔除值?fst 和 snd 仅适用于 2 值元组,我意识到您可以定义自己的函数,而不是像我那样内联它们,但似乎应该有更好的方法。

更新:我之前的折叠尝试,我不明白为什么我不能只使用 Int64 作为累加器。原来我错过了一些关键的括号。下面是更简单的版本。也消除了所有疯狂的元组提取。

ignore(foundoldfiles 
    |> Seq.filter( fun (x,y,z) ->  y = baseconfig) 
    |> Seq.fold( fun (a) (f,g,j)   -> 
        zipfilex.Add( f)
        if( a > 50L*1024L*1024L) then
            printfn("Adding 50mb to zip")
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            0L
        else
             a + j
    ) 0L
    )

更新 2:我将不得不采用命令式解决方案,F# 以某种方式重新输入此代码块,在 zip 文件在其后的语句中关闭之后。这解释了 ObjectDisposedException。不知道这是如何工作的或为什么。

4

4 回答 4

4

作为“脏”命令式风格的替代方案,您可以Seq使用通用且可重用的分块功能扩展模块。该函数有点像fold,但它需要一个返回的 lambda option<'State>。如果它返回None,则启动一个新块,否则将元素添加到前一个块中。然后你可以写一个优雅的解决方案:

files
|> Seq.filter(fun (x, y, z) ->  y = baseconfig) 
|> Seq.chunkBy(fun (x, y, z) sum -> 
     if sum + z > 50L*1024L*1024L then None
     else Some(sum + z)) 0L
|> Seq.iter(fun files ->
    zipfilex.BeginUpdate()
    for f, _, _ in files do zipfilex.Add(f)
    zipfilex.CommitUpdate())

chunkBy函数的实现有点长 - 它需要IEnumerator直接使用并且可以使用递归来表示:

module Seq = 
  let chunkBy f initst (files:seq<_>) = 
    let en = files.GetEnumerator()
    let rec loop chunk st = seq {
      if not (en.MoveNext()) then
        if chunk <> [] then yield chunk
      else
        match f en.Current st with
        | Some(nst) -> yield! loop (en.Current::chunk) nst
        | None -> 
            yield chunk 
            yield! loop [en.Current] initst }
    loop [] initst
于 2011-04-15T16:15:21.857 回答
2

我认为您的问题不会从使用fold. 它在构建不可变结构时最有用。在这种情况下,我的观点是,它使您尝试做的事情变得不那么清晰。命令式解决方案效果很好:

let mutable a = 0L
for (f, g, j) in foundoldfiles do
    if g = baseconfig then
        zipfilex.Add(f)
        if a > 50L * 1024L * 1024L then
            printfn "Adding 50mb to zip"
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            a <- 0L
        else
            a <- a + j
于 2011-04-15T16:06:41.690 回答
1

这是我的看法:

let inline zip a b = a, b

foundoldfiles 
|> Seq.filter (fun (_, internalid, _) -> internalid = baseconfig)
|> zip 0L
||> Seq.fold (fun acc (filename, _, filesize) -> 
    zipfilex.Add filename
    let acc = acc + filesize
    if acc > 50L*1024L*1024L then
        printfn "Adding 50mb to zip"
        zipfilex.CommitUpdate ()
        zipfilex.BeginUpdate ()
        0L
    else acc)
|> ignore

一些注意事项:

  • 辅助函数可以在zip没有任何开销的情况下通过整个函数清理管道,并且在更复杂的场景中有助于类型推断,因为状态从仿函数的右侧转移到左侧fold(尽管这无关紧要或有帮助在这种特殊情况下)
  • 使用_来本地丢弃不需要的元组元素使代码更易于阅读
  • 流水线化ignore而不是用额外的括号包裹整个表达式的方法使代码更易于阅读
  • 将一元函数的参数括在括号中看起来很奇怪;您不能将括号用于非一元柯里化函数,因此将它们用于一元函数是不一致的。我的策略是为构造函数调用和元组函数调用保留括号

编辑: PSif( a > 50L*1024L*1024L) then是不正确的逻辑——if需要考虑累加器加上当前文件大小。例如,如果第一个文件 >= 50MB,则 if 不会触发。

于 2011-04-15T15:52:13.803 回答
1

如果您不喜欢可变变量和命令式循环,您可以随时使用GOTO函数循环重写它:

let rec loop acc = function
    | (file, id, size) :: files ->
        if id = baseconfig then
            zipfilex.Add file
            if acc > 50L*1024L*1024L then
                printfn "Adding 50mb to zip"
                zipfilex.CommitUpdate()
                zipfilex.BeginUpdate()
                loop 0L files
            else
                loop (acc + size) files
        else
            loop acc files
    | [] -> ()

loop 0L foundoldfiles

这样做的好处是它明确地说明了归纳案例可以进行的三种不同方式以及在每种情况下如何转换累加器(因此您不太可能出错 - 见证 Daniel 的 for 循环版本中的错误)。

您甚至可以将 baseconfig 检查移到 when 子句中:

let rec loop acc = function
    | (file, id, size) :: files when id = baseconfig ->
        zipfilex.Add file
        if acc > 50L*1024L*1024L then
            printfn "Adding 50mb to zip"
            zipfilex.CommitUpdate()
            zipfilex.BeginUpdate()
            loop 0L files
        else
            loop (acc + size) files
    | _ :: files -> loop acc files
    | [] -> ()

loop 0L foundoldfiles
于 2011-04-15T20:19:47.437 回答