【问题标题】:Parsing incoming TCP stream of Ascii chars, handle backspace char解析 Ascii 字符的传入 TCP 流,处理退格字符
【发布时间】:2015-12-24 09:41:04
【问题描述】:

我需要解析来自套接字的输入流。 数据是从 Telnet 客户端发送的,因此我想通过在流中查找第一个 '\r' 字符来处理传入的字符串,然后选择返回字符之前的字节,最后处理任何 backspace '\b' 字符。

在这里处理'\b' 位的惯用方法是什么? 我目前正在使用一个可变堆栈并将字符推送到它上面,如果有退格,我会弹出最后一个字符。 然后把结果变成一个字符串。

但我认为可能有一些很好的方法可以通过模式匹配和尾递归来做到这一点。 那么,如何以 F# 的方式完成呢?

let receiveInput (inputBuffer:StringBuilder) (received:Tcp.Received)=
    let text = Encoding.ASCII.GetString(received.Data.ToArray());
    inputBuffer.Append(text) |> ignore

    let all = inputBuffer.ToString()
    match all.IndexOf('\r') with
    | enter when enter >= 0 ->
        let textToProcess = all.Substring(0,enter)
        inputBuffer.Remove(0,enter+2) |> ignore

        //this is the part I'm wondering about
        let stack = new Stack<char>()
        for c in textToProcess do
            if c = '\b' then stack.Pop() |> ignore
            else stack.Push c

        let input = new System.String(stack |> Seq.rev |> Seq.toArray)

        Some(input)
    | _ ->
        None

【问题讨论】:

    标签: f# ascii telnet


    【解决方案1】:

    感觉这样可以通过减少来完成?如果不是退格,则将字符放入累加器,如果是,则将累加器设置为尾部?

    【讨论】:

    • 应该是折叠,而不是减少,如 Mark Seemann 的回答中所述。
    【解决方案2】:

    让我们首先将有问题的部分隔离到一个函数中:

    open System
    open System.Collections.Generic
    
    let handleBackspaces textToProcess : string =
        let stack = Stack<char>()
        for c in textToProcess do
            if c = '\b' then stack.Pop() |> ignore
            else stack.Push c
        stack |> Seq.rev |> Seq.toArray |> String
    

    这有一个可变变量 (stack)。每当您有一个变异变量时,您都可以用递归函数中的累加器值替换它。这是一种方法:

    open System
    
    let handleBackspaces' textToProcess : string =
        let rec imp acc = function
            | [] -> acc
            | '\b'::cs -> imp (acc |> List.tail) cs
            | c::cs -> imp (c::acc) cs
        textToProcess |> Seq.toList |> imp [] |> List.rev |> List.toArray |> String
    

    您会注意到我已经为acc 调用了累加器值。 imp 函数的类型为char list -&gt; char list -&gt; char list,它匹配传入的char list:如果为空,则返回累加器;如果它有'\b'作为头部,它使用List.tail从累加器中删除前一个char;在所有其他情况下,它将第一个 char 转换为累加器并递归调用自身。

    这是一个(希望是令人满意的)FSI 会议:

    > handleBackspaces' "b\bfoo";;
    val it : string = "foo"
    > handleBackspaces' "foo";;
    val it : string = "foo"
    > handleBackspaces' "bar\bz";;
    val it : string = "baz"
    > handleBackspaces' "bar\b\boo";;
    val it : string = "boo"
    > handleBackspaces' "b\bfa\boo";;
    val it : string = "foo"
    

    正如 Ryan W Gough 指出的那样,一旦了解如何将某事物建模为递归函数,就应该可以使用折叠来实现它。这是一种方法:

    let handleBackspaces'' textToProcess : string =
        textToProcess
        |> Seq.fold (fun acc c -> if c = '\b' then acc |> List.tail else c::acc) []
        |> List.rev
        |> List.toArray
        |> String
    

    【讨论】:

    • 啊对,应该是fold不是reduce,在f#中没意识到区别
    • @RyanWGough reducefold 类似,但输入为空时会崩溃。
    • 谢谢,太棒了,这正是我想要的。
    猜你喜欢
    • 2011-10-06
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 2019-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多