Dividere un file in più oggetti in modo funzionale

3

Voglio leggere un file in una raccolta di oggetti. I dati (è un file Leica PTX se sei curioso) sono formattati come segue:

640 [begin item #1: number of columns]
480 [number of rows]
0 0 0 [position information: 3x3 matrix]
0 0 0 [position ctd]
0 0 0 [position ctd]
1 0 0 0 [calibration information: 3x4 matrix]
0 1 0 0 [calibration ctd]
0 0 1 0 [calibration ctd]
1.1 2.2 3.3 0.5 [point 1: X Y Z I]
-0.2 2.3 1.4 0.2 [point 2: X Y Z I]
...
3.9 1.2 -7.7 0.8 [point n: X Y Z I]
640 [begin item #2: number of columns]
480 [number of rows]
0 0 0 [position information]
0 0 0
0 0 0
1 0 0 0 [calibration information]
0 1 0 0 
0 0 1 0
0.2 1.3 -2.1 0.4 [point 1 of item #2]
... [etc]

vale a dire. Due righe ciascuna contenenti un singolo float / double (come testo) segnalano l'inizio di un nuovo elemento e nessun'altra riga è esattamente un numero.

È facile da fare se c'è un solo articolo per file, è una semplice operazione fold . Sarebbe anche relativamente semplice fare con un ciclo di tipo while che mantiene lo stato, ma sono nuovo alla programmazione funzionale e mi chiedo se non c'è un modo più conciso ed elegante per farlo a livello funzionale con strumenti standard (come reduce , fold , split / partition , ecc.). Le migliori soluzioni che ho escogitato finora implicano "sbirciare" avanti usando liste o array, ma vorrei qualcosa che permetta la possibilità di una sequenza che viene consumata mentre itera per la massima generalità. (Sto usando F # e .NET ma non vedo questo come un problema specifico della lingua.)

    
posta Robotman 26.10.2015 - 10:33
fonte

3 risposte

2

Se l'intero file è abbastanza piccolo da adattarsi alla memoria, allora penso che un modo sarebbe quello di analizzare il file in una lista e quindi elaborare l'elenco pezzo per pezzo, qualcosa del tipo:

let parseItem (columnsString::rowsString::rest) =
    let columns = int columnsString
    let rows = int rowsString
    let position, rest = parsePosition rest
    let calibration, rest = parseCalibration rest
    let points, rest = parsePoints columns rows rest
    (columns, rows, position, calibration, points), rest

let rec parseItems = function
    | [] -> []
    | lines ->
        let item, restLines = parseItem lines
        let restItems = parseItems restLines
        item :: restItems

Come hai sicuramente notato, questo codice è piuttosto ripetitivo, ma puoi semplificarlo usando il flusso di lavoro dello stato (noto come monade di stato in Haskell) da ExtCore :

let parseItem =
    state {
        let! columns = parseInt
        let! rows = parseInt
        let! position = parsePosition
        let! calibration = parseCalibration
        let! points = parsePoints columns rows
        return columns, rows, position, calibration, points
    }

let rec parseItems = function
    | [] -> [], []
    | lines ->
        state {
            let! item = parseItem
            let! restItems = parseItems
            return item::restItems
        } <| lines
    
risposta data 26.10.2015 - 19:26
fonte
3

Scommetto che lo strumento più popolare nel mondo Haskell per questo tipo di problema è combinatori di parser . La libreria classica per questo è parsec , ma per un formato di file generato dalla macchina il attoparsec è in genere una scelta migliore (analisi più veloce a costo di una segnalazione di errori meno amichevole, un buon compromesso quando si leggono file di grandi dimensioni che non sono stati scritti dagli umani) .

C'è un eccellente tutorial che dimostra l'uso di attoparsec dal log di analisi file .

Attoparsec è abbastanza popolare che qualcuno ha provato a portarlo su F # , ma non ho mai nemmeno scritto un programma F #, quindi Sicuramente non ho modo di giudicare la qualità di quella libreria. C'è anche una porta della libreria regolare (non "atto") di Haskell su F # , che potrebbe valere la pena guardare viene fornito anche con un tutorial .

Se desideri comprendere un po 'come i parser combinatori funzionano sotto il cofano, Graham Hutton e Erik Meijer hanno scritto due semplici tutorial che mostrano gli interni semplificati. Il primo, credo, è una versione ridotta del secondo:

Questi sono in Haskell, ma se conosci F # probabilmente sarai in grado di seguirne molto.

    
risposta data 07.11.2015 - 02:40
fonte
1

Questo non è esattamente uno dei migliori casi di utilizzo della programmazione funzionale, ma la maggior parte dei linguaggi ha un costrutto che creerà un flusso infinito di linee, che generalmente si alimentano in fold con uno stato iniziale. Ecco un esempio in Scala (mi dispiace, non so F #):

val lines = io.Source.stdin.getLines
val state = lines.foldLeft(initialState)(processLine)

Fondamentalmente, la funzione processLine è qualsiasi cosa tu inseriresti nel tuo ciclo while , e restituisce ogni volta un nuovo stato. Al suo interno, puoi usare split , regex, pattern matching o anche parser combinatori se sei coraggioso.

Non posso affermare che la soluzione funzionale finisca più elegante con qualcosa di questo stato, ma ha il vantaggio che il percorso sbagliato di solito è ovvio abbastanza rapidamente.

    
risposta data 26.10.2015 - 16:45
fonte

Leggi altre domande sui tag