Решение на CSV Filter от Николай Георгиев

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

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

Резултати

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

Код

use std::collections::HashMap;
use std::io::BufRead;
use std::io::Write;
use std::io::BufReader;
use std::fmt::{self, Debug, Formatter};
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
type Row = HashMap<String, String>;
//#[derive(Debug)]
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
impl<R: BufRead> Debug for Csv<R>{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write! {f,r#"CSV\nColumns: {:?}"#,self.columns}
}
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header = String::new();
let result = reader.read_line(&mut header);
match result{
Ok(0) => return Err(CsvError::InvalidHeader(String::from(""))),
Ok(_) => {
let mut i = 0;
let mut last_i = 0;
let mut order: Vec<&str> = Vec::new();
let mut unique: HashMap<&str, &str> = HashMap::new();
for c in header.chars(){
if c == ',' {
let v = &header[last_i..i];
last_i = i;
if unique.get(v) == None {
unique.insert(v, "");
order.push(v);
}else{
return Err(CsvError::InvalidHeader(String::from("")))
}
}
i += 1;
}
let v = &header[last_i..header.len()];
if unique.get(v) == None {
unique.insert(v, "");
order.push(v);
}else{
return Err(CsvError::InvalidHeader(String::from("")))
}
let mut i = 1;
let mut stringVec: Vec<String> = Vec::new();
stringVec.push(order[0].to_string());
while i < order.len() {
let s = skip_next(order[i],',');
if s != None {
stringVec.push(s.unwrap().to_string().trim().to_string());
}
i += 1;
}
return Ok(
Csv{
columns: stringVec,
reader: reader,
selection: None
}
)
},
Err(e) => return Err(CsvError::IO(e))
}
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let line = line.trim();
let mut startedCol = false;
let mut finishedCol = true;
let mut colCount = 0;
let mut colStartIndex = 0;
let mut i = 0;
let mut row: Row = HashMap::new();
if skip_next(line,'"') != None {
for c in line.chars(){
if c == ',' {
if finishedCol == true && startedCol == false {
row.insert(self.columns[colCount].clone(),line[colStartIndex..i].to_string());
colCount += 1;
colStartIndex = i + 1;
if colCount >= self.columns.len(){
return Err(CsvError::InvalidRow(String::from("Too many columns")))
}
}
}else if c == '"'{
if startedCol != finishedCol{
finishedCol = !finishedCol;
startedCol = !startedCol;
}
}
i += 1;
}
row.insert(self.columns[colCount].clone(),line[colStartIndex..i].to_string());
}else{
return Err(CsvError::InvalidRow(String::from("No opening mark")))
}
let n = self.columns.len();
if row.len() != n{
return Err(CsvError::InvalidRow(String::from("Invalid number of column")))
}
let mut col = "";
for i in 0..n{
col = row.get(&self.columns[i]).unwrap().trim();
col = &col[1..col.len()-1];
row.insert(self.columns[i].clone(),col.to_string());
}
if startedCol == true{
return Err(CsvError::InvalidRow(String::from("No closing mark")))
}else{
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 n = self.columns.len();
for i in 0..n{
if i != n - 1 {
write!(writer, "{}, ", &self.columns.get(i).unwrap());
}else{
write!(writer, "{}\n", &self.columns.get(i).unwrap());
}
}
while let Some(row) = self.next() {
match row{
Ok(r) =>{
for i in 0..n{
if i != n - 1 {
write!(writer, "{:?}, ",r.get(&self.columns[i]).unwrap());
}else{
write!(writer, "{:?}\n",r.get(&self.columns[i]).unwrap());
}
}
Ok(())
},
Err(e) => Err(e)
};
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
match self.reader.read_line(&mut line){
Ok(0) => None,
Ok(_) => {
let row = self.parse_line(&line[..]);
match row{
Ok(r) => {
match &self.selection{
Some(func) =>{
match func(&r){
Ok(true) => Some(Ok(r)),
Ok(false) => self.next(),
Err(e) => Some(Err(e))
}
},
None => Some(Err(CsvError::ParseError(String::from("No selection function availvable"))))
}
},
Err(e) => Some(Err(e))
}
},
Err(e) => Some(Err(CsvError::IO(e)))
}
}
}
pub fn skip_next(input: &str, target: char) -> Option<&str> {
match input.chars().next(){
Some(t) => {
if t == target{
Some(&input[1..])
}else{
None
}
},
_ => None
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let mut i = 0;
for c in input.chars(){
if c == target{
if i != 0{
return (&input[0..i], &input[i..])
}else{
return ("", &input[..])
}
}
i += 1;
}
(input,"")
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let tuple = take_until(input,target);
match tuple {
(_,"") => None,
(var1,var2) => Some((var1,skip_next(var2, target).unwrap()))
}
}

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

Compiling solution v0.1.0 (/tmp/d20210111-1538662-k46fly/solution)
warning: unused import: `std::io::BufReader`
 --> src/lib.rs:4:5
  |
4 | use std::io::BufReader;
  |     ^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: value assigned to `col` is never read
   --> src/lib.rs:139:13
    |
139 |         let mut col = "";
    |             ^^^^^^^
    |
    = note: `#[warn(unused_assignments)]` on by default
    = help: maybe it is overwritten before being read?

warning: cannot borrow `row` as mutable because it is also borrowed as immutable
   --> src/lib.rs:143:13
    |
141 |             col = row.get(&self.columns[i]).unwrap().trim();
    |                   --- immutable borrow occurs here
142 |             col = &col[1..col.len()-1];
143 |             row.insert(self.columns[i].clone(),col.to_string());
    |             ^^^ mutable borrow occurs here     --- immutable borrow later used here
    |
    = note: `#[warn(mutable_borrow_reservation_conflict)]` on by default
    = warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future
    = note: for more information, see issue #59159 <https://github.com/rust-lang/rust/issues/59159>

warning: variable `stringVec` should have a snake case name
  --> src/lib.rs:70:25
   |
70 |                 let mut stringVec: Vec<String> = Vec::new();
   |                         ^^^^^^^^^ help: convert the identifier to snake case: `string_vec`
   |
   = note: `#[warn(non_snake_case)]` on by default

warning: variable `startedCol` should have a snake case name
  --> src/lib.rs:98:17
   |
98 |         let mut startedCol = false;
   |                 ^^^^^^^^^^ help: convert the identifier to snake case: `started_col`

warning: variable `finishedCol` should have a snake case name
  --> src/lib.rs:99:17
   |
99 |         let mut finishedCol = true;
   |                 ^^^^^^^^^^^ help: convert the identifier to snake case: `finished_col`

warning: variable `colCount` should have a snake case name
   --> src/lib.rs:100:17
    |
100 |         let mut colCount = 0;
    |                 ^^^^^^^^ help: convert the identifier to snake case: `col_count`

warning: variable `colStartIndex` should have a snake case name
   --> src/lib.rs:101:17
    |
101 |         let mut colStartIndex = 0;
    |                 ^^^^^^^^^^^^^ help: convert the identifier to snake case: `col_start_index`

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:164:17
    |
164 |                 write!(writer, "{}, ", &self.columns.get(i).unwrap());
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(unused_must_use)]` on by default
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:166:17
    |
166 |                 write!(writer, "{}\n", &self.columns.get(i).unwrap());
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:171:13
    |
171 | /             match row{
172 | |                 Ok(r) =>{
173 | |                     for i in 0..n{
174 | |                         if i != n - 1 {
...   |
182 | |                 Err(e) => Err(e)
183 | |             };
    | |______________^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:175:29
    |
175 | ...                   write!(writer, "{:?}, ",r.get(&self.columns[i]).unwrap());
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:177:29
    |
177 | ...                   write!(writer, "{:?}\n",r.get(&self.columns[i]).unwrap());
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: 13 warnings emitted

    Finished test [unoptimized + debuginfo] target(s) in 4.28s
     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 ... ok
test solution_test::test_parsing_helpers_for_unicode ... FAILED
test solution_test::test_skip_next ... ok
test solution_test::test_take_and_skip ... FAILED
test solution_test::test_take_until ... FAILED

failures:

---- solution_test::test_csv_basic stdout ----
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError("No selection function availvable")', 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: ParseError("No selection function availvable")', 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:92:5

---- solution_test::test_csv_iterating_with_no_selection stdout ----
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError("No selection function availvable")', tests/solution_test.rs:195:48
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError("No selection function availvable")', tests/solution_test.rs:185:5

---- solution_test::test_csv_writing_without_a_selection stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date"]`,
 right: `["name, age, birth date", "\"Douglas Adams\", \"42\", \"1952-03-11\"", "\"Gen Z. Person\", \"20\", \"2000-01-01\"", "\"Ada Lovelace\", \"36\", \"1815-12-10\""]`', tests/solution_test.rs:240:9
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date"]`,
 right: `["name, age, birth date", "\"Douglas Adams\", \"42\", \"1952-03-11\"", "\"Gen Z. Person\", \"20\", \"2000-01-01\"", "\"Ada Lovelace\", \"36\", \"1815-12-10\""]`', tests/solution_test.rs:224:5

---- solution_test::test_parsing_helpers_for_unicode stdout ----
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside '↓' (bytes 0..3) of `↓яга`', src/lib.rs:227:23

---- solution_test::test_take_and_skip stdout ----
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/lib.rs:259:59

---- solution_test::test_take_until stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `("ба", "ба/яга")`,
 right: `("баба", "/яга")`', tests/solution_test.rs:121: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_parsing_helpers_for_unicode
    solution_test::test_take_and_skip
    solution_test::test_take_until

test result: FAILED. 8 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test solution_test'

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

Николай качи първо решение на 28.12.2020 14:02 (преди почти 5 години)