解析传入的 ASCII 字符流,处理退格字符。

7
我需要解析来自套接字的输入流。数据是从 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
2个回答

12

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

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)。每当您有一个可变变量时,您可以在递归函数中使用累加器值来替换它。以下是一种方法:

这里有一个单一的可变变量(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

你会注意到,我已经将累加器的值称为accimp函数的类型为char list -> char list -> 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#中的区别。 - Ryan W Gough
3
reducefold类似,但当输入为空时会崩溃。 - Mark Seemann
谢谢,太棒了,这正是我在寻找的。 - Roger Johansson

1
似乎可以用reduce来完成?如果不是退格符,则将字符添加到累加器中,否则只需将累加器设置为其尾部。

应该使用 fold 而不是 reduce,正如 Mark Seemann 的回答中所提到的。 - Ryan W Gough

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接