2015-09-26 12 views
7

Devo analizzare i flussi di input da un socket. I dati vengono inviati da un client Telnet e pertanto desidero elaborare le stringhe in entrata trovando il primo carattere '\r' nello stream, quindi selezionare i byte prima del carattere di ritorno e infine elaborare qualsiasi carattere backspace'\b'.Analizzare il flusso TCP in ingresso di caratteri Ascii, gestire il carattere di backspace

Quale sarebbe il modo idiomatico di gestire i bit '\b' qui? Attualmente sto usando uno stack mutabile e spingo caratteri su di esso, e se c'è un backspace, inserisco l'ultimo carattere. Quindi basta trasformare il risultato in una stringa.

Ma immagino ci sia probabilmente un bel modo per farlo con il pattern matching e la ricorsione della coda. Quindi, come può essere fatto il modo 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 

risposta

11

Let isolando la parte problematica ad una funzione:

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 

Questo ha una singola variabile mutabile (stack). Ogni volta che si ha una variabile mutante, è possibile sostituirla con un valore dell'accumulatore in una funzione ricorsiva. Ecco un modo per farlo:

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 

Avrete notato che ho chiamato il valore dell'accumulatore per acc. La funzione imp ha il tipo char list -> char list -> char list e corrisponde al numero char list in arrivo: se è vuoto, restituisce l'accumulatore; se ha la testa '\b', rimuove il precedente char dall'accumulatore utilizzando List.tail; e in tutti gli altri casi, viene addebitato al primo char all'accumulatore e si chiama in modo ricorsivo.

Ecco un (si spera soddisfacente) sessione di 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" 

Una volta che si comprende come modellare qualcosa come una funzione ricorsiva, dovrebbe essere possibile per la sua attuazione mediante una piega, invece, come sottolinea Ryan W Gough . Ecco un modo per farlo:

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

Ah sì, dovrebbe essere fold non ridurre, non aveva capito la differenza in f # –

+3

@RyanWGough 'reduce' è simile a' fold', ma si blocca quando l'input è vuoto. –

+0

Grazie, fantastico, è esattamente quello che stavo cercando. –

1

Si può fare una riduzione simile? Consita il personaggio dell'accumulatore se non è un backspace, se è quindi sufficiente impostare l'accumulatore sulla coda? inizio

+0

Dovrebbe essere una piega, non ridurre, come menzionato nella risposta di Mark Seemann. –

Problemi correlati