Владение и время жизни во вложенных итераторах в ржавчине для сравнения строк - PullRequest
0 голосов
/ 18 января 2019

Я погружаю свои пальцы в Rust и не могу понять, как сравнивать строки двух CSV-файлов. Я подозреваю, что моя трудность возникает из-за попыток решить проблему совершенно неверным образом, и поэтому я использую все преимущества stackoverflow.

Я пишу простую программу, которая читает два файла csv известных полей, а затем сравнивает расстояние редактирования каждого элемента столбца j в csv1 с каждым элементом столбца j в csv2 для всех столбцов j в J. На данный момент Мой код только сравнивает первую строку csv1 со всеми строками csv2.

Мой шаблон:

  1. считайте csvs в struct Reader, используя csv и serde (все хорошо).
  2. Создайте struct Compare, который содержит две строки типа Reader, по одной от каждого CSV.
  3. Напишите метод для Compare, который возвращает строковые расстояния.

У меня есть основные фрагменты кода ниже, и все это можно найти на ржавой площадке здесь .

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,
}

Здесь я реализую метод для сравнения, который вычисляет расстояние Джаро-Винклера для каждого элемента двух строк и возвращает другой тип структуры, определенный в другом месте (полный файл см. Выше в ссылке на ржавую площадку выше):

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);
    }
}

Я пытался исправить свои проблемы, инициализируя объект comp перед вторым циклом for, но я не могу заставить его работать. Для инициализации требуется метод по умолчанию, который я пытался написать для записи. Я думаю, что у меня все заработало, но потом возникли проблемы, потому что время жизни объекта, который я назначил во втором цикле for, было слишком коротким и не сохранялось достаточно долго, чтобы его можно было напечатать. Это убедило меня в том, что я, возможно, решаю проблему неправильно.

Заранее извиняюсь: это педагогический проект, поэтому я здесь, чтобы получить образование.

1 Ответ

0 голосов
/ 18 января 2019

Проблема с кодом, как есть, на самом деле не очень связана с 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());
    }
}
...