首页 > 解决方案 > 如何使用 CSVProvider 加载具有不同结构的 csv?

问题描述

有任何想法吗?我当前的代码如下。

let rawdata csvfile 
        FinCsv.Load(data).Rows
        |> Seq.filter (fun row -> row.Id <> "---")
        |> Seq.filter (fun row -> row.Country <> "Honduras")
        |> Seq.filter (fun row -> row.Tax <> 0)
        |> Seq.groupBy (fun row -> row.Country)
        |> Seq.averageBy (fun row -> row.Tax)
        |> List.ofSeq

标签: csvf#

解决方案


静态解析类型参数的问题是语法很麻烦。这是一个例子:

let inline snippet< ^T when ^T : (member Id : string)
                        and ^T : (member Country : string)
                        and ^T : (member Tax : float) >
                        xs =
    xs
    |> Seq.filter (fun row -> ( ^T : (member Id : string) row) <> "---")
    |> Seq.filter (fun row -> ( ^T : (member Country : string) row) <> "South America")
    |> Seq.filter (fun row -> ( ^T : (member Tax : float) row) <> 0.0)
    |> Seq.groupBy (fun row -> ( ^T : (member Country : string) row))
    |> Seq.map (fun (country, rows) -> country, (rows |> Seq.averageBy (fun row -> ( ^T : (member Tax : float) row))))
    |> List.ofSeq

(我已经修改了 averageBy 调用,所以它比问题中的更有意义。)

您实际上可以让编译器推断约束而不是显式声明它们:

let inline snippet xs =
    xs
    |> Seq.filter (fun row -> ( ^T : (member Id : string) row) <> "---")
    |> Seq.filter (fun row -> ( ^T : (member Country : string) row) <> "South America")
    |> Seq.filter (fun row -> ( ^T : (member Tax : float) row) <> 0.0)
    |> Seq.groupBy (fun row -> ( ^T : (member Country : string) row))
    |> Seq.map (fun (country, rows) -> country, (rows |> Seq.averageBy (fun row -> ( ^T : (member Tax : float) row))))
    |> List.ofSeq

作为替代方案,您可以要求调用者传递 value-getter 函数:

let snippet2 getId getCountry getTax xs =
    xs
    |> Seq.filter (fun row -> getId row <> "---")
    |> Seq.filter (fun row -> getCountry row <> "South America")
    |> Seq.filter (fun row -> getTax row <> 0.0)
    |> Seq.groupBy (fun row -> getCountry row)
    |> Seq.map (fun (country, rows) -> country, (rows |> Seq.averageBy (fun row -> getTax row))))
    |> List.ofSeq

这使您可以稍微简化语法:

let snippet2 getId getCountry getTax xs =
    xs
    |> Seq.filter (getId >> (<>) "---")
    |> Seq.filter (getCountry >> (<>) "South America")
    |> Seq.filter (getTax >> (<>) 0.0)
    |> Seq.groupBy getCountry
    |> Seq.map (fun (country, rows) -> country, (rows |> Seq.averageBy getTax))
    |> List.ofSeq

您可以使用部分应用程序声明函数的特定类型实例:

let snippetForCsvFile1 (xs : CsvFile1 seq) = xs |> snippet2 (fun r -> r.Id) (fun r -> r.Country) (fun r -> r.Tax)

然后您的开关可能如下所示:

let rawdata csvfile =
    match csvfile with
    | 1 -> snippetForCsvFile1 CsvFile1
    | 2 -> snippetForCsvFile2 CsvFile2
    | 3 -> snippetForCsvFile3 CsvFile3
    | 4 -> snippetForCsvFile4 CsvFile4
    | _ -> failwith "Not a File"

那么这应该与您的最后一个表达式一起使用:

let raw =
    [ 1 .. 4]
    |> List.collect rawdata

您问题中的开关有一个您在问题中没有提到的问题:

let rawdata csvfile =
    let data = 
        match csvfile with
        | 1 -> CsvFile1
        | 2 -> CsvFile2
        | 3 -> CsvFile3
        | 4 -> CsvFile4
        | _ -> failwith "Not a File"

这里的问题是 的绑定data没有单一类型。您可以使用接口(或基类)通过面向对象的方法来实现这一点,但这对于 F# 来说并不是特别惯用的。一种更惯用的方法是声明一个有区别的联合,但鉴于您发布的示例,它似乎没有必要。


推荐阅读