Последовательность в F # складывающихся троек - PullRequest
1 голос
/ 15 апреля 2011

Я гуглил и читал, и я пытаюсь найти «правильный» способ сделать это, но каждый вопрос, который я читаю на SO, кажется, имеет совершенно разные ответы.

Вот суть моей проблемы. У файлов есть сигнатура типа seq тройки (a: строка, b: строка, 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-архив (но на самом деле, он просто попадает в список для последующей фиксации), и когда файлы превышать 50 МБ, зафиксируйте ожидающие файлы в zip-архиве. Добавление файла - это дешево, фиксация - это дорого, поэтому я стараюсь снизить стоимость, группируя его.

Пока что код вроде работает ... За исключением исключения ObjectDisposedException, которое я получил, когда оно приблизилось к 150 МБ зафиксированных файлов. Но я не уверен, что это правильный способ сделать такую ​​операцию. Такое чувство, что я использую Seq.fold нетрадиционным способом, но пока я не знаю лучшего способа сделать это.

Бонусный вопрос: есть ли лучший способ вырезать значения из кортежей? fst и snd работают только для двухзначных кортежей, и я понимаю, что вы можете определять свои собственные функции вместо встроенных, как я, но, похоже, должен быть лучший способ.

Обновление: мои предыдущие попытки сбрасывать, я не мог понять, почему я не мог просто использовать 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 голосов
/ 15 апреля 2011

В качестве альтернативы «грязному» императивному стилю вы можете расширить модуль Seq общей и многократно используемой функцией для разбиения на фрагменты.Функция немного похожа на fold, но требует лямбду, которая возвращает 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
2 голосов
/ 15 апреля 2011

Я не думаю, что ваша проблема выигрывает от использования 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
1 голос
/ 16 апреля 2011

Если вам не нравятся изменяемые переменные и императивные циклы, вы всегда можете переписать это, используя 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

Преимущество этого в том, что он явно заявляеттри различных способа, которыми может развиваться индуктивный случай, и способ преобразования аккумулятора в каждом случае (так что вы вряд ли поймете это неправильно - посмотрите на ошибку в циклической версии Дэниела).

Вы могли бы дажепереместите проверку 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
1 голос
/ 15 апреля 2011

Вот мое мнение:

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 вместо переноса всего выражения с дополнительными скобками облегчает чтение кода
  • Заключение аргументов унарных функций в круглые скобки выглядит странно;Вы не можете использовать скобки для не унарных функций с карри, поэтому использование их для унарных функций противоречиво.Моя политика заключается в том, чтобы зарезервировать круглые скобки для вызовов конструктора и вызовов tupled-функций

EDIT : PS if( a > 50L*1024L*1024L) then - неверная логика - if необходимо учитыватьаккумулятор плюс текущий размер файла.Например, если первый файл был> = 50 МБ, тогда if не будет срабатывать.

...