2015-09-26 14 views
7

Potrzebuję przeanalizować strumienie wejściowe z gniazda. Dane są wysyłane z klienta Telnet, a zatem chcę przetworzyć ciągi przychodzące, znajdując pierwszy znak '\r' w strumieniu, a następnie wybierz bajty przed zwracaniem znaku i ostatecznie przetworzyć dowolny znak backspace'\b'.Parsowanie przychodzącego strumienia TCP znaków Ascii, uchwyt Backspace char

Jaki byłby idiomatyczny sposób radzenia sobie z bitami '\b' tutaj? Aktualnie używam stosu zmiennego i wciskam na niego znaków, a jeśli jest tam backspace, wyskakuję ostatni znak. Następnie wystarczy przekształcić wynik w ciąg znaków.

Ale myślę, że jest prawdopodobnie jakiś dobry sposób, aby to zrobić z dopasowaniem do wzorca i rekurencją ogona. Jak można to zrobić w sposó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 

Odpowiedz

11

Let poprzez izolowanie problematyczną część do funkcji:

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 

ten ma jedną zmienną zmienny (stack). Za każdym razem, gdy masz zmienną mutującą, możesz zastąpić ją wartością akumulatora w funkcji rekursywnej. Oto jeden ze sposobów, aby to zrobić:

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 

Zauważysz, że ja o nazwie wartość akumulatora do acc. Funkcja imp ma typ char list -> char list -> char list i pasuje do przychodzącego char list: jeśli jest pusta, zwraca akumulator; jeśli ma on '\b' jako głowę, usuwa poprzedni char z akumulatora za pomocą List.tail; a we wszystkich innych przypadkach pierwsza z nich to char do akumulatora i wywołuje się rekurencyjnie.

Oto (mamy nadzieję zadowalająca) FSI sesja:

> 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" 

Raz jeden wie, jak modelować coś jako funkcji rekurencyjnej, powinno być możliwe do wdrożenia go za pomocą fałd zamiast, jak Ryan W Gough zaznacza . Oto jeden sposób, aby to zrobić:

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 
+0

Ach tak, powinno się spasować, nie zmniejszać, nie zdawałem sobie sprawy z różnicy w f # –

+3

@RyanWGough 'reduce' jest podobne do' fold', ale ulega awarii, gdy wejście jest puste. –

+0

Dzięki, niesamowite, właśnie tego szukałem. –

1

Wygląda na to, że można to zrobić przy pomocy redukcji? Spójrz z postacią na akumulator, jeśli nie jest to backspace, czy po prostu ustaw akumulator na ogonie? początek

+0

Powinny być składane, nie zmniejszać, jak wspomniano w odpowiedzi Marka Seemanna. –