Решение на CSV Filter от Ася Русанова

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

Към профила на Ася Русанова

Резултати

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

Код

pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut iterator = input.chars();
let current_char = iterator.next();
match current_char {
Some(char) => {
if char == target {
let len_target = target.len_utf8();
let(_,result) = input.split_at(len_target);
return Some(result);
} else {
return None;
}
}
_ => return None,
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let mut iterator = input.char_indices();
let mut current_char = iterator.next();
while current_char != None {
match current_char {
Some((position,char)) => {
if char == target {
return input.split_at(position);
} else {
current_char = iterator.next();
}
}
_ => return (input, "")
}
}
return (input, "");
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (str1,str2) = take_until(input,target);
if str2 == "" {
return None;
} else {
let str2_without_target = skip_next(str2, target).unwrap();
return Some((str1, &str2_without_target));
}
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
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 header = String::new();
let _ = match reader.read_line(&mut header) {
Ok(bytes) => {
if bytes == 0 {
return Err(CsvError::InvalidHeader(String::from("Header: does not contain column names")));
} else {
let mut columns: Vec<String> = Vec::new();
let mut separated_column = take_and_skip(&header, ',');
while separated_column != None {
match separated_column {
Some((curr_column, rest)) => {
let curr_column_trimmed = curr_column.trim();
let rest_trimmed = rest.trim();
if columns.iter().any(|i| i == curr_column) {
return Err(CsvError::InvalidHeader(String::from("Header: contains duplicated columns")));
} else {
columns.push(String::from(curr_column_trimmed));
separated_column = take_and_skip(rest_trimmed, ',');
if separated_column == None && !rest_trimmed.is_empty() {
columns.push(String::from(rest_trimmed));
}
}
}
None => return Err(CsvError::InvalidHeader(String::from("Unexpected error: Csv::new(mut reader: R) - separated_column == None")))
}
}
return Ok(Csv {
columns: columns,
reader: reader,
selection: None
});
}
}
Err(e) => return Err(CsvError::IO(e))
};
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let line_trimmed = line.trim();
let mut row: Row = HashMap::new();
let mut position = 0;
let fields_count = self.columns.len();
let mut without_first_quote = skip_next(line_trimmed, '\"');
while without_first_quote != None {
match without_first_quote {
Some(remaining) => {
match take_and_skip(remaining, '\"') {
Some((current_value, rem)) => {
row.insert(self.columns[position].clone(), String::from(current_value));
position = position + 1;
if rem.is_empty() {
if position == fields_count {
return Ok(row);
} else {
return Err(CsvError::InvalidRow(String::from("Line: empty, keys: remaining")));
}
} else {
if position == fields_count {
return Err(CsvError::InvalidRow(String::from("Line: not empty, keys: empty")));
} else {
let (_,next_values) = take_until(rem, '\"');
without_first_quote = skip_next(next_values, '\"');
}
}
}
None => return Err(CsvError::InvalidRow(String::from("No closing quote in the remaining value")))
}
}
_ => return Err(CsvError::InvalidRow(String::from("Unexpected error")))
}
}
return Err(CsvError::InvalidRow(String::from("Next value: does not start with an opening quote")));
}
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 columns_count = self.columns.len();
let mut position = 0;
while position <= columns_count - 2 {
write!(&mut writer, "{}, ", self.columns[position].clone())?;
position = position + 1;
}
if position == columns_count - 1 {
write!(&mut writer, "{}", self.columns[position].clone())?;
}
position = 0;
let mut curr_row = self.next();
loop {
match curr_row {
Some(value) => {
match value {
Ok(row) => {
write!(&mut writer, "\n")?;
while position <= columns_count - 2 {
write!(&mut writer, "\"{}\", ", row.get(&self.columns[position]).unwrap())?;
position = position + 1;
}
if position == columns_count - 1 {
write!(&mut writer, "\"{}\"", row.get(&self.columns[position]).unwrap())?;
}
position = 0;
curr_row = self.next();
}
Err(e) => return Err(e)
}
}
None => return 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();
loop {
let _ = match self.reader.read_line(&mut line) {
Ok(bytes) => {
if bytes == 0 {
return None;
} else {
let _ = match self.parse_line(&line) {
Ok(row) => {
match &self.selection {
Some(boxed_func) => {
match (*boxed_func)(&row) {
Ok(true) => return Some(Ok(row)),
Ok(false) => line.clear(),
Err(e) => return Some(Err(e))
}
}
None => return Some(Ok(row))
}
}
Err(e) => return Some(Err(e))
};
}
}
Err(e) => return Some(Err(CsvError::IO(e)))
};
}
}
}

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

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

running 15 tests
test solution_test::test_csv_basic ... ok
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 ... 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 ... FAILED
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_duplicate_columns stdout ----
thread 'main' panicked at 'Expression None does not match the pattern "Some(CsvError::InvalidHeader(_))"', tests/solution_test.rs:92:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- solution_test::test_csv_single_column_no_data stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `0`,
 right: `1`', tests/solution_test.rs:178:5


failures:
    solution_test::test_csv_duplicate_columns
    solution_test::test_csv_single_column_no_data

test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Ася качи първо решение на 11.01.2021 10:29 (преди 6 месеца)