Решение на CSV Filter от Ивайло Кирязов
Резултати
- 7 точки от тестове
- 0 бонус точки
- 7 точки общо
- 7 успешни тест(а)
- 8 неуспешни тест(а)
Код
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input != "" && input.chars().next().unwrap() == target {
return Some(&input[1..]);
}
None
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
if skip_next(input, target) != None {
return (&input[0..1], skip_next(input, target).unwrap());
}
for (i, self_value) in input.chars().enumerate() {
if self_value == target {
return (&input[0..i], &input[i..]);
}
}
(input, "")
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
if skip_next(input, target) != None {
return Some(("", skip_next(input, target).unwrap()));
}
for (i, self_value) in input.chars().enumerate() {
if self_value == target {
return Some((&input[0..i], &input[i + 1..]));
}
}
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 line = String::new();
let read = reader.read_line(&mut line);
if read.is_err() {
return Err(CsvError::IO(read.unwrap_err()));
}
if read.ok() == Some(0) {
return Err(CsvError::InvalidHeader("Zero Bytes read!".to_string()));
}
let mut trimmed = line.trim();
if skip_next(trimmed, ',') != None {
return Err(CsvError::InvalidHeader(
"Can't start with empty column!".to_string(),
));
}
// name, age, height -> ,age, height
let mut col = vec![];
let first_col = take_until(trimmed, ',');
col.push(first_col.0.to_string());
trimmed = first_col.1;
while skip_next(trimmed, ',') != None {
//,age, height -> age, height
trimmed = skip_next(trimmed, ',').unwrap();
//,age, height -> , height and so on...
let first_col = take_until(trimmed, ',');
col.push(first_col.0.trim().to_string());
trimmed = first_col.1;
}
for (pos, val) in col.iter().enumerate() {
for (pos_second, val_second) in col.iter().enumerate() {
if val == val_second && pos != pos_second {
return Err(CsvError::InvalidHeader("No duplicates!".to_string()));
}
}
}
let res = Csv {
columns: col,
reader: reader,
selection: None,
};
Ok(res)
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut res = Row::new();
let mut trimmed = line.trim();
if skip_next(trimmed, '"') == None {
return Err(CsvError::InvalidRow(
"Can't start with empty column!".to_string(),
));
}
let mut rows_ok = false;
for (num, col) in self.columns.iter().enumerate() {
trimmed = trimmed.trim();
if skip_next(trimmed, '"') == None {
return Err(CsvError::InvalidRow(
"Enter first captions on your data".to_string(),
));
}
//"Gen Z. Person", "20", "2000-01-01" -> Gen Z. Person", "20", "2000-01-01"
trimmed = skip_next(trimmed, '"').unwrap();
//Gen Z. Person", "20", "2000-01-01" -> , "20", "2000-01-01"
let data = take_and_skip(trimmed, '"');
if data == None {
return Err(CsvError::InvalidRow(
"Enter end captions on your data".to_string(),
));
}
res.insert(col.to_string(), data.unwrap().0.trim().to_string());
trimmed = data.unwrap().1;
if trimmed == "" {
if num + 1 != self.columns.len() {
return Err(CsvError::InvalidRow("Not Enough Rows".to_string()));
}
rows_ok = true;
break;
}
trimmed = &trimmed[1..];
}
if !rows_ok {
return Err(CsvError::InvalidRow("Too many rows".to_string()));
}
Ok(res)
}
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 word = Vec::new();
if self.columns.len() == 0 {
return Err(CsvError::InvalidHeader(
"We don't have a header".to_string(),
));
}
for (num, col) in self.columns.iter().enumerate() {
let str = String::from(col);
let mut word = Vec::from(str);
if num + 1 != self.columns.len() {
word.push(',' as u8);
word.push(' ' as u8);
}
let res = writer.write(&word);
if res.is_err() {
return Err(CsvError::IO(res.unwrap_err()));
}
}
word = vec!['\n' as u8];
let res = writer.write(&word);
if res.is_err() {
return Err(CsvError::IO(res.unwrap_err()));
}
let mut line = String::new();
let mut res = self.reader.read_line(&mut line);
if res.is_err() {
return Err(CsvError::IO(res.unwrap_err()));
}
while res.ok() != Some(0) {
let row = self.parse_line(&mut line);
if row.is_err() {
return Err(row.unwrap_err());
}
let is_ok = self.selection.as_ref().unwrap()(&row.as_ref().unwrap());
if is_ok.is_err() {
return Err(is_ok.unwrap_err());
}
if is_ok.unwrap() {
let map = row.unwrap();
for (num, col) in self.columns.iter().enumerate() {
word = vec!['"' as u8];
let res = writer.write(&word);
if res.is_err() {
return Err(CsvError::IO(res.unwrap_err()));
}
word = Vec::from(map[col].clone());
word.push('"' as u8);
if num + 1 != self.columns.len() {
word.push(',' as u8);
word.push(' ' as u8);
}
let res = writer.write(&word);
if res.is_err() {
return Err(CsvError::IO(res.unwrap_err()));
}
}
}
res = self.reader.read_line(&mut line);
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
if self.columns.len() == 0 {
return Some(Err(CsvError::InvalidHeader(
"We don't have a header".to_string(),
)));
}
loop {
let mut line = String::new();
let res_read = self.reader.read_line(&mut line);
if res_read.is_err() {
return Some(Err(CsvError::IO(res_read.unwrap_err())));
}
if res_read.ok() == Some(0) {
break;
}
let res = self.parse_line(&line.to_string());
if res.is_err() {
return Some(Err(res.err().unwrap()));
}
let is_ok = self.selection.as_ref().unwrap()(&res.as_ref().unwrap());
if is_ok.is_err() {
return Some(Err(is_ok.err().unwrap()));
}
if is_ok.is_ok() && is_ok.unwrap() {
return Some(Ok(res.unwrap()));
} else {
continue;
}
}
None
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20210111-1538662-yc8gdh/solution) warning: value assigned to `word` is never read --> src/lib.rs:165:13 | 165 | let mut word = Vec::new(); | ^^^^^^^^ | = note: `#[warn(unused_assignments)]` on by default = help: maybe it is overwritten before being read? warning: 1 warning 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 ... 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 ... FAILED test solution_test::test_csv_parse_line_with_commas ... ok test solution_test::test_csv_selection_and_writing ... FAILED 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 `Option::unwrap()` on a `None` value', /tmp/d20210111-1538662-yc8gdh/solution/src/lib.rs:252:49 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 'called `Option::unwrap()` on a `None` value', /tmp/d20210111-1538662-yc8gdh/solution/src/lib.rs:252:49 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:185:5 ---- solution_test::test_csv_parse_line stdout ---- thread 'main' panicked at 'assertion failed: `(left == right)` left: `("Name With Spaces", "13", "0-0-0")`, right: `(" Name With Spaces ", " 13 ", "0-0-0")`', tests/solution_test.rs:151:5 ---- solution_test::test_csv_selection_and_writing stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Too many rows")', tests/solution_test.rs:263:35 thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: InvalidRow("Too many rows")', tests/solution_test.rs:251:5 ---- solution_test::test_csv_writing_without_a_selection stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /tmp/d20210111-1538662-yc8gdh/solution/src/lib.rs:198:49 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', 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:3:22 ---- solution_test::test_take_and_skip stdout ---- thread 'main' panicked at 'byte index 5 is not a char boundary; it is inside 'б' (bytes 4..6) of `баба/яга`', src/lib.rs:26:41 ---- 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_iterating_with_no_selection solution_test::test_csv_parse_line solution_test::test_csv_selection_and_writing 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. 7 passed; 8 failed; 0 ignored; 0 measured; 0 filtered out error: test failed, to rerun pass '--test solution_test'