[英]F# map a seq to another seq of shorter length
我有這樣的字符串序列(文件中的行)
[20150101] error a
details 1
details 2
[20150101] error b
details
[20150101] error c
我正在嘗試將其映射到這樣的字符串序列(日志條目)
[20150101] error a details 1 details 2
[20150101] error b details
[20150101] error c
我可以通過命令式方式(通過翻譯我將用C#編寫的代碼)來做到這一點-可以工作,但它的讀取方式類似於偽代碼,因為我省略了引用的函數:
let getLogEntries logFilePath =
seq {
let logEntryLines = new ResizeArray<string>()
for lineOfText in getLinesOfText logFilePath do
if isStartOfNewLogEntry lineOfText && logEntryLines.Any() then
yield joinLines logEntryLines
logEntryLines.Clear()
logEntryLines.Add(lineOfText)
if logEntryLines.Any() then
yield joinLines logEntryLines
}
有更實用的方法嗎?
我不能使用Seq.map
因為它不是一對一的映射,而且Seq.fold
似乎不正確,因為我懷疑它會在返回結果之前處理整個輸入序列(如果我有非常大的日志文件,效果Seq.fold
)。 我認為上面的代碼不是在F#中執行此操作的理想方法,因為它使用的是ResizeArray<string>
。
通常,當沒有可用的內置函數時,解決問題的功能方法是使用遞歸。 在這里,您可以遞歸地遍歷輸入,記住最后一個塊的內容(因為最后一個[xyz] Info
行),並在到達新的起始塊時產生新的結果。 在F#中,您可以使用序列表達式很好地編寫此代碼:
let rec joinDetails (lines:string list) lastChunk = seq {
match lines with
| [] ->
// We are at the end - if there are any records left, produce a new item!
if lastChunk <> [] then yield String.concat " " (List.rev lastChunk)
| line::lines when line.StartsWith("[") ->
// New block starting. Produce a new item and then start a new chunk
if lastChunk <> [] then yield String.concat " " (List.rev lastChunk)
yield! joinDetails lines [line]
| line::lines ->
// Ordinary line - just add it to the last chunk that we're collection
yield! joinDetails lines (line::lastChunk) }
這是一個顯示正在運行的代碼的示例:
let lines =
[ "[20150101] error a"
"details 1"
"details 2"
"[20150101] error b"
"details"
"[20150101] error c" ]
joinDetails lines []
Seq
內置的功能不足以幫助您,因此您必須推出自己的解決方案。 最終,像這樣解析文件涉及迭代和維護狀態,但是F#所做的是通過計算表達式封裝了該迭代和狀態(因此您將使用seq
計算表達式)。
您所做的事情還不錯,但是您可以將代碼提取到一個通用函數中,該函數在不了解格式的情況下按輸入序列計算塊 (即字符串序列)。 剩下的部分,即解析實際的日志文件,可以使之完全起作用。
過去,我已經編寫了此功能來幫助解決此問題。
let chunkBy chunkIdentifier source =
seq {
let chunk = ref []
for sourceItem in source do
let isNewChunk = chunkIdentifier sourceItem
if isNewChunk && !chunk <> [] then
yield !chunk
chunk := [ sourceItem ]
else chunk := !chunk @ [ sourceItem ]
yield !chunk
}
它需要一個chunkIdentifier
函數,如果輸入是新塊的開始,則該函數返回true。
解析日志文件只是提取行,計算塊並連接每個塊的一種情況:
logEntryLines |> chunkBy (fun line -> line.[0] = '[')
|> Seq.map (fun s -> String.Join (" ", s))
通過盡可能多地封裝迭代和變異,同時創建可重用的函數,這更符合函數式編程的精神。
另外,還有兩個變體:
let lst = ["[20150101] error a";
"details 1";
"details 2";
"[20150101] error b";
"details";
"[20150101] error c";]
let fun1 (xs:string list) =
let sb = new System.Text.StringBuilder(xs.Head)
xs.Tail
|> Seq.iter(fun x -> match x.[0] with
| '[' -> sb.Append("\n" + x)
| _ -> sb.Append(" " + x)
|> ignore)
sb.ToString()
lst |> fun1 |> printfn "%s"
printfn "";
let fun2 (xs:string list) =
List.fold(fun acc (x:string) -> acc +
match x.[0] with| '[' -> "\n" | _ -> " "
+ x) xs.Head xs.Tail
lst |> fun2 |> printfn "%s"
打印:
[20150101] error a details 1 details 2
[20150101] error b details
[20150101] error c
[20150101] error a details 1 details 2
[20150101] error b details
[20150101] error c
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.