Решение на CSV Filter от Ивайло Атовски
Резултати
- 9 точки от тестове
- 0 бонус точки
- 9 точки общо
- 9 успешни тест(а)
- 6 неуспешни тест(а)
Код
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input.is_empty(){
return None;
}
let mut chars = input.chars();
match chars.next().unwrap() {
target => Some(chars.as_str()),
_ => None
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let byte = input.find(target);
match byte{
Some(x) => input.split_at(x),
None => (input, "")
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (str1, str2) = take_until(input, target);
match skip_next(str2, target){
Some(str3) => Some( (str1, str3) ),
None => None
}
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
use std::io::Write;
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut buffer: String = String::new();
match reader.read_line(&mut buffer) {
Ok(x) if x!=0 => {
let mut aMap = Row::new();
let mut columns = Vec::<String>::new();
let mut str_rest =String::new();
match take_and_skip(&buffer, ','){
Some((str1,str2)) =>{
if !aMap.contains_key(str1){
aMap.insert(str1.trim().to_string(), "".to_string());
columns.push(str1.trim().to_string());
str_rest = str2.to_string();
}
else{
return Err(CsvError::InvalidHeader(String::from("Repetition")));
}
}
None => {
buffer = buffer.trim().trim_end_matches("\n").to_string();
if !aMap.contains_key(&buffer){
aMap.insert(buffer.trim().to_string(), "".to_string());
columns.push(buffer.trim().to_string());
return Ok(Self{columns: columns, reader: reader, selection: None});
}
else{
return Err(CsvError::InvalidHeader(String::from("Repetition")));
}
}
};
while let Some((str1, str2)) = take_and_skip(&str_rest, ','){
if !aMap.contains_key(str1){
aMap.insert(str1.trim().to_string(), "".to_string());
columns.push(str1.trim().to_string());
str_rest = str2.to_string();
}
else{
return Err(CsvError::InvalidHeader(String::from("Repetition")));
}
}
str_rest = str_rest.trim().trim_end_matches("\n").to_string();
if !aMap.contains_key(&str_rest){
aMap.insert(str_rest.clone(), "".to_string());
columns.push(str_rest.clone());
}
else{
return Err(CsvError::InvalidHeader(String::from("Repetition")));
}
Ok(Self{columns: columns, reader: reader, selection: None})
}
Ok(x) if x== 0 => Err(CsvError::InvalidHeader(String::from("InvalidHeader new"))),
Ok(_) => Err(CsvError::InvalidHeader(String::from("InvalidHeader new"))),
Err(e) => Err(CsvError::IO(e) )
}
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let mut val = String::from("");
let str_line = line.trim().trim_end_matches("\n").to_string();
if str_line.chars().next().unwrap() != '"'{
return Err(CsvError::InvalidRow("Invalid Row - Не започва с \" ".to_string()));
}
let mut is_open =false;
let max_column: usize = self.columns.len();
let mut index: usize = 0;
for ch in str_line.chars() {
if ch == '"' && !is_open {
if index == max_column{
return Err(CsvError::InvalidRow("Invalid Row- Колоните са по малко от колкото на реда".to_string()));
}
is_open = true;
val.clear();
}
else if ch == '"' && is_open{
row.insert(self.columns[index].clone(), val.clone());
is_open = false;
index +=1;
}else{
val.push(ch);
}
}
if is_open{
return Err(CsvError::InvalidRow("Invalid Row- не е затворена \" накрая".to_string()));
}
if index != max_column{
return Err(CsvError::InvalidRow("Invalid Row- Колоните са повече от колкото на реда".to_string()));
}
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 string_all = String::new();
let col = self.columns.clone();
let col_size = col.len();
for index in 0..col_size {
string_all.push(' ');
string_all.push_str(col[index].clone().as_str());
string_all.push(',')
}
string_all.pop();
string_all.push('\n');
for elem in self {
match elem {
Ok(row) => {
for index in 0..col_size {
let key = col[index].clone();
let val = match row.get(&key){
Some(iner_val) => iner_val.clone(),
None => unreachable!()
};
string_all.push('"');
string_all.push_str(val.as_str());
string_all.push('"');
string_all.push_str(", ");
}
string_all = string_all.trim().to_string();
string_all.pop();
string_all.push('\n');
},
Err(e) => return Err(e)
}
}
match writer.write(string_all.as_bytes()){
Ok(_) => Ok(()),
Err(e) => {
Err(CsvError::IO(e))
}
}
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buffer: String = String::new();
match self.reader.read_line(&mut buffer) {
Ok(x) if x!=0 => {
match self.parse_line(&buffer){
Ok(row) => {
match &self.selection{
Some(box_fn)=>{
let is_ok = match box_fn(&row) {
Ok(is_ok) =>is_ok,
Err(e)=>return Some(Err(e))
};
if is_ok{
Some(Ok(row))
}
else {
self.next()
}
},
None=>{
Some(Err(CsvError::InvalidRow("Не е сложено Условие".to_string())))
}
}
},
Err(e)=> Some(Err(e))
}
},
Ok(x) if x == 0 => None,
Ok(_) => unreachable!(),
Err(e) => Some(Err(CsvError::IO(e) ))
}
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20210111-1538662-3f0wc3/solution) warning: unreachable pattern --> src/lib.rs:10:9 | 9 | target => Some(chars.as_str()), | ------ matches any value 10 | _ => None | ^ unreachable pattern | = note: `#[warn(unreachable_patterns)]` on by default warning: unused variable: `target` --> src/lib.rs:9:9 | 9 | target => Some(chars.as_str()), | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_target` | = note: `#[warn(unused_variables)]` on by default warning: unused variable: `target` --> src/lib.rs:1:31 | 1 | pub fn skip_next(input: &str, target: char) -> Option<&str> { | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_target` warning: value assigned to `str_rest` is never read --> src/lib.rs:60:21 | 60 | let mut str_rest =String::new(); | ^^^^^^^^^^^^ | = note: `#[warn(unused_assignments)]` on by default = help: maybe it is overwritten before being read? warning: variable does not need to be mutable --> src/lib.rs:156:31 | 156 | pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> { | ----^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default warning: variable `aMap` should have a snake case name --> src/lib.rs:58:25 | 58 | let mut aMap = Row::new(); | ^^^^ help: convert the identifier to snake case: `a_map` | = note: `#[warn(non_snake_case)]` on by default warning: 6 warnings emitted Finished test [unoptimized + debuginfo] target(s) in 4.06s Running target/debug/deps/solution_test-8916805fc40a2dab running 15 tests test solution_test::test_csv_basic ... FAILED test solution_test::test_csv_duplicate_columns ... FAILED 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 ... FAILED test solution_test::test_csv_writing_without_any_rows ... FAILED test solution_test::test_parsing_helpers_for_unicode ... ok test solution_test::test_skip_next ... FAILED 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 `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:70:39 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:60:5 ---- solution_test::test_csv_duplicate_columns stdout ---- thread 'main' panicked at 'Expression None does not match the pattern "Some(CsvError::InvalidHeader(_))"', tests/solution_test.rs:106:5 ---- solution_test::test_csv_iterating_with_no_selection stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:195:48 thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:185:5 ---- solution_test::test_csv_writing_without_a_selection stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:234:35 thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Не е сложено Условие")', tests/solution_test.rs:224:5 ---- solution_test::test_csv_writing_without_any_rows stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `[" name, age, birth date"]`, right: `["name, age, birth date"]`', tests/solution_test.rs:291:9 thread 'main' panicked at 'assertion failed: `(left == right)` left: `[" name, age, birth date"]`, right: `["name, age, birth date"]`', tests/solution_test.rs:278:5 ---- solution_test::test_skip_next stdout ---- thread 'main' panicked at 'assertion failed: `(left == right)` left: `Some("test]")`, right: `None`', tests/solution_test.rs:114:5 failures: solution_test::test_csv_basic solution_test::test_csv_duplicate_columns solution_test::test_csv_iterating_with_no_selection solution_test::test_csv_writing_without_a_selection solution_test::test_csv_writing_without_any_rows solution_test::test_skip_next test result: FAILED. 9 passed; 6 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--test solution_test'