Решение на CSV Filter от Кирил Костов
Резултати
- 13 точки от тестове
- 0 бонус точки
- 13 точки общо
- 13 успешни тест(а)
- 2 неуспешни тест(а)
Код
use std::collections::HashMap;
use std::io::{BufRead, Write};
type Row = HashMap<String, String>;
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input.is_empty() || input.chars().next().unwrap() != target {
None
} else {
Some(input.split_at(target.len_utf8()).1)
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let index = input
.chars()
.take_while(|x| *x != target)
.map(|x| x.len_utf8())
.fold(0, |sum, i| sum + i);
input.split_at(index)
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let index = input
.chars()
.take_while(|x| *x != target)
.map(|x| x.len_utf8())
.fold(0, |sum, i| sum + i);
if input.is_empty() || index == input.bytes().count() {
None
} else {
Some((&input[..index], &input[index + target.len_utf8()..]))
}
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut buffer = String::new();
let result = reader.read_line(&mut buffer);
match result {
Ok(bytes) if bytes == 0 => {
return Err(CsvError::InvalidHeader(String::from("Empty header.")))
}
Ok(_) => (),
Err(e) => return Err(CsvError::IO(e)),
}
let mut columns: Vec<String> = Vec::new();
loop {
match take_and_skip(buffer.as_str(), ',') {
Some(res) => {
let (column, rest_of_line) = res;
if columns.contains(&column.trim().to_string()) {
return Err(CsvError::InvalidHeader(String::from("Duplicate column.")));
}
columns.push(column.trim().to_string());
buffer = rest_of_line.to_string();
}
None if buffer != "" => {
if columns.contains(&buffer.trim().to_string()) {
return Err(CsvError::InvalidHeader(String::from("Duplicate column.")));
}
columns.push(buffer.trim().to_string());
buffer = String::from("");
}
None => break,
}
}
Ok(Csv {
columns: columns,
reader: reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut buffer = line.trim();
let mut row: Row = HashMap::new();
for column in self.columns.iter() {
buffer = match skip_next(buffer, '"') {
Some(res) => res,
None => {
return Err(CsvError::InvalidRow(String::from(
"Row does not start with \".",
)))
}
};
match take_and_skip(buffer, '"') {
Some(res) => {
let (col_value, rest_of_line) = res;
row.insert(column.to_string(), col_value.to_string());
buffer = rest_of_line.trim_start();
}
None => return Err(CsvError::InvalidRow(String::from("No closing \"."))),
}
buffer = match skip_next(buffer, ',') {
Some(res) => res.trim_start(),
None => buffer,
}
}
if !buffer.is_empty() {
return Err(CsvError::InvalidRow(String::from("Row has more columns.")));
}
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));
}
fn csv_write_to<W: Write>(buffer: &String, writer: &mut W) -> Result<(), CsvError> {
match writer.write(buffer.as_bytes()) {
Ok(_) => Ok(()),
Err(e) => Err(CsvError::IO(e)),
}
}
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let columns_count = self.columns.len();
for (i, column) in self.columns.iter().enumerate() {
if i != columns_count - 1 {
Self::csv_write_to(&format!("{}, ", column), &mut writer)?;
} else {
Self::csv_write_to(&format!("{}\n", column), &mut writer)?;
}
}
loop {
let mut buffer = String::new();
let result = self.reader.read_line(&mut buffer);
match result {
Ok(bytes) if bytes == 0 => break,
Ok(_) => (),
Err(e) => return Err(CsvError::IO(e)),
}
let row = self.parse_line(&buffer)?;
match self.selection {
Some(ref func) => {
if !(*func)(&row)? {
continue;
}
}
None => (),
}
for (i, column) in self.columns.iter().enumerate() {
let column_value = row.get(column).unwrap();
if i != columns_count - 1 {
Self::csv_write_to(&format!("\"{}\", ", column_value), &mut writer)?;
} else {
Self::csv_write_to(&format!("\"{}\"\n", column_value), &mut writer)?;
}
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut buffer = String::new();
let result = self.reader.read_line(&mut buffer);
match result {
Ok(bytes) if bytes == 0 => return None,
Ok(_byte) => (),
Err(e) => return Some(Err(CsvError::IO(e))),
}
let row = self.parse_line(&buffer).ok()?;
match self.selection {
Some(ref func) => {
if !(*func)(&row).ok()? {
continue;
} else {
return Some(Ok(row));
}
}
None => (),
}
}
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20210111-1538662-1h4mwla/solution) Finished test [unoptimized + debuginfo] target(s) in 4.47s Running target/debug/deps/solution_test-8916805fc40a2dab running 15 tests test solution_test::test_csv_basic ... FAILED 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 ... FAILED 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 failures: ---- solution_test::test_csv_basic stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:70:30 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:60:5 ---- solution_test::test_csv_iterating_with_no_selection stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `[]`, right: `["Douglas Adams", "Gen Z. Person", "Ada Lovelace"]`', tests/solution_test.rs:196:9 thread 'main' panicked at 'assertion failed: `(left == right)` left: `[]`, right: `["Douglas Adams", "Gen Z. Person", "Ada Lovelace"]`', tests/solution_test.rs:185:5 failures: solution_test::test_csv_basic solution_test::test_csv_iterating_with_no_selection test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--test solution_test'