zoukankan      html  css  js  c++  java
  • 【译】用 Rust 实现 csv 解析-part6

    写入 tab 分隔符的值

    在前面的章节中,我们了解了如何将 CSV 数据输出到标准输出中,如下:

    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    你可能会想:如果都是这么简单地数据,那使用 CSV writer 还有什么意义?CSV writer 的好处是,它能处理所有类型的数据,而不会牺牲数据的完整性。也就是说,它知道何时引用包含特殊 CSV 字符的字段(如逗号或者换行),或在数据中出现的转义字面量引号。CSV writer 还可以方便地配置使用不同地分隔符或引用策略。

    在这一节中,我们将看看如何调整 CSV writer 上的设置。特别地,我们将使用 TSV(“制表符分割值”)来替代 CSV,并且我们要求 CSV writer 引用所有非数字字段。这里有一个例子:

    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::WriterBuilder::new()
            .delimiter(b'	')
            .quote_style(csv::QuoteStyle::NonNumeric)
            .from_writer(io::stdout());
    
        wtr.write_record(&["City", "State", "Population", "Latitude", "Longitude"])?;
        wtr.write_record(&["Davidsons Landing", "AK", "", "65.2419444", "-165.2716667"])?;
        wtr.write_record(&["Kenai", "AK", "7610", "60.5544444", "-151.2583333"])?;
        wtr.write_record(&["Oakman", "AL", "", "33.7133333", "-87.3886111"])?;
    
        wtr.flush()?;
        Ok(())
    }
    

    编译并运行这个例子:

    $ cargo build
    $ ./target/debug/csvtutor
    "City"  "State" "Population"    "Latitude"      "Longitude"
    "Davidsons Landing"     "AK"    ""      65.2419444      -165.2716667
    "Kenai" "AK"    7610    60.5544444      -151.2583333
    "Oakman"        "AL"    ""      33.7133333      -87.3886111
    

    在本例中,我们使用了一个新类型 QuoteStyleQuoteStyle 类型表示你可以使用不同的引用策略。默认情况下,只在必要时才向字段值周围添加引号。这可能适用于大多数例子,但你也可以一直在字段两边添加引号,或不添加引号,或者只在非数字两边加引号。

    使用 Serde 写入

    正如 CSV reader 支持将数据自动反序列化为 Rust 类型数据一样,CSV writer 也支持那样使用 Serde 将 Rust 类型数据序列化为 CSV 记录数据。在本节中,我们就学习怎么使用它。

    与读一样,我们先看看如何序列化一个 Rust 元组。

    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::Writer::from_writer(io::stdout());
    
        // 我们仍然需要手动地写入头部
        wtr.write_record(&["City", "State", "Population", "Latitude", "Longitude"])?;
    
        // 但现在我们可以通过提供的常用的 Rust 值,写入记录。
        //
        // 注意,奇数列的 `None::<u64>` 预发是必须的,因为 `None` 本身没有具体的类型,但 Serde 需要一个具体的类型以便进行序列化。也就是说,`None` 的类型 `Option<T>`,而 `None::<u64>` 的类型是 `Option<u64>`。
        wtr.serialize(("Davidsons Landing", "AK", None::<u64>, 65.2419444, -165.2716667))?;
        wtr.serialize(("Kenai", "AK", Some(7610), 60.5544444, -151.2583333))?;
        wtr.serialize(("Oakman", "AL", None::<u64>, 33.7133333, -87.3886111))?;
    
        wtr.flush()?;
        Ok(())
    }
    

    编译并运行这个程序,期望的输出如下:

    $ cargo build
    $ ./target/debug/csvtutor
    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    在上面的例子中,需要注意的关键点是,使用 serialize 而不是 write_record 进行写入数据。特别地,write_record 在写入仅包含字符串数据的简单记录时使用。另一方面,当数据包含更复杂的值时,如数字、浮点数、可选值时,则使用 serialize。当然,你总是可以将复杂的值转为字符串,然后再使用 write_record 统一写入,但 Serde 可以为你自动的完成上面那些繁琐的工作。

    和读一样,我们也可以将自定义结构序列化为 CSV 记录。这样的好处是,结构体中的字段将被识别为头记录。

    要将自定义结构写入 CSV 记录,我们需要再次使用 serde_derive crate。正如前面章节的使用 Serde 读取数据 所述,我们需要在我们的 Cargo.toml 中的 [dependencies] 区块下加上两个 crate 依赖声明(如果没有就加上)。

    serde = "1"
    serde_derive = "1"
    

    我们还需要在代码中加上两行外部库的引入代码 extern crate ,如下方所示:

    extern crate csv;
    extern crate serde;
     #[macro_use]
    extern crate serde_derive;
    
    use std::error::Error;
    use std::io;
    use std::process;
    
    // 记住结构体可以派生两个 trait:Serialize 和 Deserialize!
     #[derive(Debug, Serialize)]
     #[serde(rename_all = "PascalCase")]
    struct Record<'a> {
        city: &'a str,
        state: &'a str,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    
    fn run() -> Result<(), Box<Error>> {
        let mut wtr = csv::Writer::from_writer(io::stdout());
    
        wtr.serialize(Record {
            city: "Davidsons Landing",
            state: "AK",
            population: None,
            latitude: 65.2419444,
            longitude: -165.2716667,
        })?;
        wtr.serialize(Record {
            city: "Kenai",
            state: "AK",
            population: Some(7610),
            latitude: 60.5544444,
            longitude: -151.2583333,
        })?;
        wtr.serialize(Record {
            city: "Oakman",
            state: "AL",
            population: None,
            latitude: 33.7133333,
            longitude: -87.3886111,
        })?;
    
        wtr.flush()?;
        Ok(())
    }
    
    fn main() {
        if let Err(err) = run() {
            println!("{}", err);
            process::exit(1);
        }
    }
    

    编译并运行这个示例,虽然我们没有显式地写入头部,但输出和上次是一样的。

    $ cargo build
    $ ./target/debug/csvtutor
    City,State,Population,Latitude,Longitude
    Davidsons Landing,AK,,65.2419444,-165.2716667
    Kenai,AK,7610,60.5544444,-151.2583333
    Oakman,AL,,33.7133333,-87.3886111
    

    在这个例子中,可以看到,serialize 方法上被标记上了一个结构体字段名。这样做的话,serialize 将自动写入一个头部(只要其他记录尚未写入),该记录的结构体的字段是按照他们定义的顺序组成。请注意,可以通过 WriterBuilder::has_headers 方法将此行为禁用。

    同样值得指出的是,在 Record 结构体中使用了一个生命周期参数:

    struct Record<'a> {
        city: &'a str,
        state: &'a str,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    

    'a 声明周期参数对应于 citystate 字符串切片的生命周期。这表示 Record 结构体包含了 借用 的数据。我们本可以在不借用任何数据的情况下编写结构体,这时也就不需要生命周期参数了:

    struct Record {
        city: String,
        state: String,
        population: Option<u64>,
        latitude: f64,
        longitude: f64,
    }
    

    然而,由于我们使用 String 类型替换 &str 类型,我们现在被迫为我们所写的每条记录中的 citystate 分配一个新的 String 值。这样做本身没有问题,但会有点性能浪费。

    关于序列化的更多示例和详细信息,可以参考 Writer::serialize 方法。

    Pipelining

    • 待续...
  • 相关阅读:
    测试clang-format的格式化效果
    debian设置limits.conf
    可读性公式的python包+MLTD讲解
    SQL-1
    首届中文NL2SQL挑战赛-step7记录
    首届中文NL2SQL挑战赛-step6
    torch学习中的难点
    yield and send的使用详细解释
    ELMO及前期工作 and Transformer及相关论文
    LSTM参数和结构的本质理解——num_units参数/batch_size/cell计算
  • 原文地址:https://www.cnblogs.com/ishenghuo/p/14321482.html
Copyright © 2011-2022 走看看