Разбор входящего потока TCP символов Ascii, обработка символа backspace

мне нужно проанализировать входные потоки из сокета. Данные отправляются из клиента Telnet, и поэтому я хочу обработать входящие строки, найдя первый 'r' символ в потоке, затем выберите байты перед возвращаемым символом и, наконец, обработайте любой backspace 'b' chars.

каким был бы идиоматический способ борьбы с 'b' биты здесь? В настоящее время я использую изменяемый стек и нажимаю на него символы, и если есть backspace, я открываю последний символ. Тогда просто повернуть результат в строку.

но я полагаю, что, вероятно, есть хороший способ сделать это с сопоставлением шаблонов и рекурсией хвоста. Итак, как это можно сделать способом 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 ответов


давайте начнем с выделения проблемной части функции:

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. The imp функция имеет тип char list -> char list -> char list, и он соответствует входящему char list: если он пуст, он возвращает аккумулятор; если он имеет '\b' как голова, он удаляет предыдущий char от аккумулятора с помощью List.tail; и во всех остальных случаях это минусы первого 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"

как только вы поймете, как моделировать что-то как рекурсивную функцию, должно быть возможно реализовать ее с помощью сгиба, как указывает Райан у Гоф. Есть только один способ. это:

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

чувствует, что это можно сделать с уменьшением? Минусы персонажа к аккумулятору, если это не backspace, если он тогда просто установил аккумулятор на свой хвост?