Последовательность в F# складывающихся троек
Я гуглил и читал, и я пытаюсь найти "правильный" способ сделать это, но каждый вопрос, который я читаю на 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 ответа
Я не думаю, что ваша проблема выигрывает от использования 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
В качестве альтернативы "грязному" императивному стилю вы можете продлить 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
Если вам не нравятся изменяемые переменные и императивные циклы, вы всегда можете переписать это, используя 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
Вот мое мнение:
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-функций
РЕДАКТИРОВАТЬ: PS if( a > 50L*1024L*1024L) then
неверная логика - if
Необходимо учитывать аккумулятор плюс текущий размер файла. Например, если первый файл был>= 50 МБ, тогда if не сработал бы.