解析 Ascii 字符的传入 TCP 流,处理退格字符

Parsing incoming TCP stream of Ascii chars, handle backspace char

我需要解析来自套接字的输入流。 数据是从 Telnet 客户端发送的,因此我想通过在流中找到第一个 '\r' 字符来处理传入的字符串,然后选择 return 字符之前的字节,最后处理任何 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

感觉这可以通过 reduce 来完成?如果字符不是退格键,则将字符转换为累加器,如果是则将累加器设置到它的尾部?

让我们从将有问题的部分隔离到一个函数开始:

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 -> char list -> char list,它匹配传入的 char list:如果它是空的,它是 returns 累加器;如果它以 '\b' 作为头部,它会使用 List.tail 从累加器中删除前一个 char;在所有其他情况下,它将第一个 char 转换为累加器并递归调用自身。

这是一份(希望令人满意的)FSI session:

> 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