生锈的嵌套迭代器中的所有权和生存期,用于字符串比较

时间:2019-01-18 18:04:59

标签: csv struct rust iterator

我将脚趾浸入Rust中,似乎无法弄清楚如何比较两个csv文件的行。我怀疑我的困难源于试图以完全错误的方式解决该问题,因此我将自己投身于stackoverflow的摆布。

我正在编写一个简单的程序,该程序读取两个已知字段的csv文件,然后比较J中所有j列的csv1中j列的每个元素与csv2中j列的每个元素的编辑距离。 ,我的代码只能成功将csv1的第一行与csv2的所有行进行比较。

我的模式是:

  1. 使用csv和serde板条箱将csvs读入struct Reader(都不错)。
  2. 创建一个struct Compare,其中包含两行类型为Reader的行,每行csv中的一行。
  3. 写一个Compare方法,返回字符串距离。

我下面有一些核心代码段,整个内容都可以在锈蚀的游乐场here中访问。

struct Record将保留一行,

#[derive(Debug,Deserialize)]
struct Record {
    mp: String,
    party: String,
    constit: String,
    position: String,
    group: String,
}

struct Compare将两行保持在一起。我之所以借用它的价值,是因为我不断收到复制错误-但是可以说这是我的问题开始的地方!

#[derive(Debug)]
struct Compare<'a> {
    dfa: &'a Record,
    dfb: &'a Record,
}

这里,我实现了一个Compare方法,该方法计算了两行中每个元素的Jaro-Winkler距离,并返回了在其他地方定义的另一个结构类型(有关完整文件,请参见上面的rust操场的链接):

impl <'a> Compare<'a> {
    fn jwdist(&self) -> Stringcomps {
        let res = Stringcomps {
            mp: strsim::jaro_winkler(&self.dfa.mp, &self.dfb.mp),
            party: strsim::jaro_winkler(&self.dfa.party, &self.dfb.party),
            constit: strsim::jaro_winkler(&self.dfa.constit, &self.dfb.constit),
            position: strsim::jaro_winkler(&self.dfa.position, &self.dfb.position),
            group: strsim::jaro_winkler(&self.dfa.group, &self.dfb.group),
        };
        res
    }    
}

以下代码运行该函数(带有一些玩具数据)。由于仅将第一个csv文件的第一行与其他csv文件的所有行进行比较,因此会产生错误的输出:

fn run() -> Result<(), Box<Error>> {
    // get first df
    let data1 = "mp,party,constit,position,group\n
george,con,bath,whip,no\n
bob,lab,oxford,backbench,yes";
    let data2 = "mp,party,constit,position,group\n
goerge,can,both,wihp,no\n
bob,lob,ofxord,backbenth,yes";
    let mut rdr = csv::Reader::from_reader(data1.as_bytes());
    // get second df
    let mut rdr2 = csv::Reader::from_reader(data2.as_bytes());
    // iterate through both and compare
    for result in rdr.deserialize() {
        let record: Record = result?;
        for result2 in rdr2.deserialize() {
            let record2: Record = result2?;
            let comp = Compare{
                dfa: &record,
                dfb: &record2,
            };
            println!("{:?} compared to {:?}: {:?}", comp.dfa.mp, 
            comp.dfb.mp, comp.jwdist());
        }
    }
    Ok(())
}

fn main() {
    if let Err(err) = run() {
        println!("error running example: {}", err);
        process::exit(1);
    }
}

我试图通过在第二个for循环之前初始化对象comp来解决问题,但似乎无法正常工作。初始化需要一个默认方法,我尝试为Record编写该方法。我想我可以使用它,但是后来遇到了麻烦,因为我在第二个for循环内分配的对象的生命周期太短,无法生存到足以打印的时间。这让我非常确信我可能错误地解决了这个问题。

提前道歉:这是一个教学项目,所以我在这里接受教育。

1 个答案:

答案 0 :(得分:2)

按原样,代码的问题实际上与Rust无关,但当您从阅读器中读取时,您正在消耗他们。您的代码基本上可以(使用伪代码):

file1 = open("file1");
file2 = open("file2");
for line1 in read_lines(file1):
    for line2 in read_lines(file2):
        compare(line1, line2)

file1没问题,第一次读取file2时也是如此。但是在外部循环file2的第二次迭代中,它位于文件的末尾,因此不再从中读取任何行,并且循环结束。

更简单的解决方案是每次阅读file2

file1 = open("file1");
for line1 in read_lines(file1):
    file2 = open("file2");
    for line2 in read_lines(file2):
        compare(line1, line2)

效率不是很高,因为您要一遍又一遍地读取同一文件。

如果您只想阅读一次,则可以将Records中的所有file2收集到Vec中,然后根据需要迭代Vec多次:

let mut rdr = csv::Reader::from_reader(data1.as_bytes());
let mut rdr2 = csv::Reader::from_reader(data2.as_bytes());
let lines2 = rdr2.deserialize().collect::<Result<Vec<Record>, _>>()?;

for result in rdr.deserialize() {
    let record: Record = result?;
    for record2 in &lines2 {
        let comp = Compare{
            dfa: &record,
            dfb: record2,
        };
        println!("{:?} compared to {:?}: {:?}", comp.dfa.mp, 
             comp.dfb.mp, comp.jwdist());
    }
}