【问题标题】:Parsing awkward CSV file with a dynamic number of columns gives error解析具有动态列数的尴尬 CSV 文件会出错
【发布时间】:2018-08-19 10:27:25
【问题描述】:

我是一名 C# 开发人员,这是我第一次尝试编写 F#。

我正在尝试以 CSV 格式读取 Dashlane 导出的数据库。对于每种可能的条目类型,这些文件没有标题和动态列数。以下文件是我用来测试我的软件的虚拟数据示例。它只包含password 条目,但它们有 5 到 7 列(稍后我将决定如何处理其他类型的数据) 导出文件的第一行(在这种情况下,但并非总是如此)是用于创建 dashlane 帐户的电子邮件地址,这使得该行只有一列宽。

"accountCreation@email.fr"
"Nom0","siteweb0","Identifiant0","",""
"Nom1","siteweb1","identifiant1","email1@email.email","",""
"Nom2","siteweb2","email2@email.email","",""
"Nom3","siteweb3","Identifiant3","password3",""
"Nom4","siteweb4","Identifiant4","email4@email.email","password4",""
"Nom5","siteweb5","Identifiant5","email5@email.email","SecondIdentifiant5","password5",""
"Nom6","siteweb6","Identifiant6","email6@email.email","SecondIdentifiant6","password6","this is a single-line note"
"Nom7","siteweb7","Identifiant7","email7@email.email","SecondIdentifiant7","password7","this is a 
multi
line note"
"Nom8","siteweb8","Identifiant8","email8@email.email","SecondIdentifiant8","password8","single line note"

我正在尝试将每一行的第一列打印到控制台作为开始

let rawCsv = CsvFile.Load("path\to\file.csv", ",", '"', false)       
for row in rawCsv.Rows do
    printfn "value %s" row.[0]

这段代码在for 行给我以下错误

无法根据架构解析第 2 行:预期 1 列,得到 5

我没有给CsvFile 任何架构,我在互联网上找不到如何指定架构。

如果我愿意,我可以动态删除第一行,但它不会改变任何内容,因为其他行的列数也不同。

有什么方法可以在 F# 中解析这个笨拙的 CSV 文件吗?

注意:对于每个password 行,只有最后一行之前的列对我很重要(密码列)

【问题讨论】:

    标签: f# f#-data


    【解决方案1】:

    我不认为像您这样结构不规则的 CSV 文件适合使用 CSV Type ProviderCSV Parser 进行处理。

    同时,用几行自定义逻辑将这个文件解析成你喜欢的样子似乎并不难。以下sn-p:

    open System
    open System.IO
    
    File.ReadAllLines("Sample.csv") // Get data
    |> Array.filter(fun x -> x.StartsWith("\"Nom")) // Only lines starting with "Nom may contain password
    |> Array.map (fun x -> x.Split(',') |> Array.map (fun x -> x.[1..(x.Length-2)])) // Split each line into "cells"
    |> Array.filter(fun x -> x.[x.Length-2] |> String.IsNullOrEmpty |> not) // Take only those having non-empty cell before the last one
    |> Array.map (fun x -> x.[0],x.[x.Length-2]) // show the line key and the password
    

    解析您的示例文件后生成

    >
    val it : (string * string) [] =
    [|("Nom3", "password3"); ("Nom4", "password4"); ("Nom5", "password5");
    ("Nom6", "password6"); ("Nom7", "password7"); ("Nom8", "password8")|]
    >
    

    这可能是进一步完善解析逻辑到完美的一个很好的起点。

    【讨论】:

    • 我同意可能需要 prasing 逻辑,这就是我实际上用 C# 编写库的原因(我比 F# 更适合)。我将尝试使用您的代码在 F# 中重新编写我的库。我会看看如何改变它,以便它接受任何数据集(Name 字段不一定包含Name 字符串:-) 谢谢!
    【解决方案2】:

    我建议将 csv 文件作为文本文件读取。我逐行读取文件并形成一个列表,然后使用 CsvFile.Parse 解析每一行。但问题是元素是在 Headers 中找到的,而不是在 Rows 类型的字符串 [] 选项

     open  FSharp.Data
     open System.IO
    
     let readLines (filePath:string) = seq {
         use sr = new StreamReader(filePath)
         while not sr.EndOfStream do
             yield sr.ReadLine ()
     }
    
     [<EntryPoint>]
     let main argv = 
         let lines = readLines "c:\path_to_file\example.csv"
         let rows = List.map (fun str -> CsvFile.Parse(str)) (Seq.toList lines)
         for row in List.toArray(rows) do
             printfn "New Line"
             if row.Headers.IsSome then 
                 for r in row.Headers.Value do
                     printfn "value %s" (r)
         printfn "%A" argv
         0 // return an integer exit code
    

    【讨论】:

    • 我的 CSV 文件中没有标题。这段代码还能用吗?我不明白row.Headers 在没有价值的情况下如何具有价值......
    • 另外,我最终用 C# 重写了程序,自己通过逐字符读取文件来解析文件……但我仍然对 F# 解决方案感兴趣。
    猜你喜欢
    • 2021-08-02
    • 1970-01-01
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 2014-02-19
    • 2012-09-05
    • 2011-02-16
    • 2014-10-20
    相关资源
    最近更新 更多