F#折叠三元组中的序列

| 我已经在Google上搜索并阅读了,我正试图找到一种“正确的”方式来做,但是我在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存档。添加文件很便宜,提交也很昂贵,因此我尝试通过批量处理来降低成本。 到目前为止,代码还可以工作...除了ObjectDisposedException接近150MB的提交文件时,我得到了。但是我不确定这是执行此操作的正确方法。感觉我在以非常规方式使用
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:我将不得不使用命令式解决方案,在紧随其后的语句中关闭zip文件之后,F#会以某种方式重新输入此代码块。其中解释了ObjectDisposedException。不知道它是如何工作的或为什么。     
已邀请:
我认为使用ѭ4不会使您的问题受益。在构建不可变结构时,它最有用。在这种情况下,我的看法是,这会使您尝试做的事情变得不太清楚。命令式解决方案很好地工作:
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
    
作为“脏话”命令式的替代方法,您可以使用通用的可重用函数来扩展
Seq
模块,以进行分块。该函数有点像
fold
,但是它需要一个返回
option<\'State>
的lambda。如果返回“ 9”,则开始一个新的块,否则将元素添加到前一个块。然后,您可以编写一个优雅的解决方案:
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
    
这是我的看法:
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
helper函数使整个函数的流水线整洁而没有任何开销,并且在更复杂的情况下,由于状态从
fold
函子的右侧转移到左侧,有助于类型推断(尽管不是这样)重要事项或帮助) 使用ѭ17局部丢弃不需要的元组元素使代码更易于阅读 流水线到ѭ18中的方法,而不是用多余的括号包装整个表达式,使代码更易于阅读 用括号括起一元函数的参数看起来很奇怪。您不能将括号用于非一元咖喱函数,因此将其用于一元函数是不一致的。我的政策是为构造函数调用和元组函数调用保留括号 编辑:PS
if( a > 50L*1024L*1024L) then
是不正确的逻辑-
if
需要考虑累加器和当前文件大小。例如,如果第一个文件> = 50MB,则if不会触发。     
如果您不喜欢可变变量和命令式循环,则可以始终使用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's 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
    

要回复问题请先登录注册