簡體   English   中英

F#將一個序列映射到另一個較短長度的序列

[英]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

鏈接: https//dotnetfiddle.net/3KcIwv

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM