Решение на CSV Filter от Георги Гергинов

Обратно към всички решения

Към профила на Георги Гергинов

Резултати

  • 15 точки от тестове
  • 0 бонус точки
  • 15 точки общо
  • 15 успешни тест(а)
  • 0 неуспешни тест(а)

Код

use std::io::BufRead;
use std::collections::HashMap;
use std::io::Write;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
type Row = HashMap<String, String>;
pub fn skip_next(input: &str, target: char) -> Option<&str> {
match input.starts_with(target) {
false => None,
true => input.strip_prefix(target),
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
match input.contains(target) {
false => (input, ""),
true => input.split_at(input.find(target).unwrap()),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
match input.contains(target) {
false => None,
true => Some((take_until(input, target).0, skip_next(take_until(input, target).1, target).unwrap())),
}
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header = String::new();
let read_result = reader.read_line(&mut header);
if let Err(e) = read_result {
return Err(CsvError::IO(e));
}
if let Ok(0) = read_result {
return Err(CsvError::InvalidHeader(String::from("Empty header")));
}
let columns: Vec<String> = header.split(',').map(|s| s.trim().to_string()).collect();
for i in 0..(columns.len() - 1) {
for j in (i + 1)..columns.len() {
if columns[i] == columns[j] {
return Err(CsvError::InvalidHeader(String::from("Duplicate column")));
}
}
}
Ok(Csv {
columns,
reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut line = line.trim().to_string();
if !line.starts_with('"') {
return Err(CsvError::InvalidRow(String::from("Row doesn't start with quotation mark")));
}
let num_of_quot_marks = line.matches('"').collect::<Vec<_>>().len();
if num_of_quot_marks % 2 == 1 {
return Err(CsvError::InvalidRow(String::from("Missing quotation mark")));
}
let mut words_from_line = Vec::<String>::new();
for i in 0..num_of_quot_marks {
if i % 2 == 0 {
line = skip_next(&line, '"').unwrap().to_string();
} else {
words_from_line.push(take_and_skip(&line, '"').unwrap().0.to_string());
line = take_and_skip(&line, '"').unwrap().1.to_string();
if !line.is_empty() {
line = take_until(&line, '"').1.to_string();
}
}
}
if self.columns.len() > words_from_line.len() {
return Err(CsvError::InvalidRow(String::from("Header is longer than row")))
}
if self.columns.len() < words_from_line.len() {
return Err(CsvError::InvalidRow(String::from("Header is shorter than row")))
}
let mut row = Row::new();
for i in 0..words_from_line.len() {
row.insert(self.columns[i].clone(), words_from_line[i].clone());
}
Ok(row)
}
pub fn apply_selection<F>(&mut self, callback: F)
where F: Fn(&Row) -> Result<bool, CsvError> + 'static
{
self.selection = Some(Box::new(callback));
}
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let mut stringified_header = String::new();
for i in 0..self.columns.len() {
stringified_header.push_str(&self.columns[i]);
if i != self.columns.len() - 1 {
stringified_header.push_str(", ");
}
}
stringified_header.push('\n');
if let Err(e) = writer.write(stringified_header.as_bytes()) {
return Err(CsvError::IO(e));
}
while let Some(row) = self.next() {
let curr = row.unwrap();
let mut stringified_curr = String::new();
for i in 0..self.columns.len() {
stringified_curr.push('\"');
if let Some(s) = curr.get(&self.columns[i]) {
stringified_curr.push_str(s);
}
stringified_curr.push('\"');
if i != self.columns.len() - 1 {
stringified_curr.push_str(", ");
}
}
stringified_curr.push('\n');
if let Err(e) = writer.write(stringified_curr.as_bytes()) {
return Err(CsvError::IO(e));
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buffer = String::new();
let read_result = self.reader.read_line(&mut buffer);
if let Err(e) = read_result {
return Some(Err(CsvError::IO(e)));
}
if let Ok(0) = read_result {
return None;
}
let row = match self.parse_line(&buffer) {
Err(e) => return Some(Err(e)),
Ok(r) => r,
};
match &self.selection {
None => Some(Ok(row)),
Some(f) => match f(&row) {
Err(e) => Some(Err(e)),
Ok(false) => self.next(),
Ok(true) => Some(Ok(row)),
},
}
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20210111-1538662-16gqs6r/solution)
    Finished test [unoptimized + debuginfo] target(s) in 5.96s
     Running target/debug/deps/solution_test-8916805fc40a2dab

running 15 tests
test solution_test::test_csv_basic ... ok
test solution_test::test_csv_duplicate_columns ... ok
test solution_test::test_csv_empty ... ok
test solution_test::test_csv_iterating_with_a_selection ... ok
test solution_test::test_csv_iterating_with_no_selection ... ok
test solution_test::test_csv_parse_line ... ok
test solution_test::test_csv_parse_line_with_commas ... ok
test solution_test::test_csv_selection_and_writing ... ok
test solution_test::test_csv_single_column_no_data ... ok
test solution_test::test_csv_writing_without_a_selection ... ok
test solution_test::test_csv_writing_without_any_rows ... ok
test solution_test::test_parsing_helpers_for_unicode ... ok
test solution_test::test_skip_next ... ok
test solution_test::test_take_and_skip ... ok
test solution_test::test_take_until ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

История (1 версия и 0 коментара)

Георги качи първо решение на 10.01.2021 20:03 (преди над 4 години)