Решение на CSV Filter от Георги Анастасов

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

Към профила на Георги Анастасов

Резултати

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

Код

use std::collections::HashMap;
use std::io::BufRead;
type Row = HashMap<String, String>;
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input == "" {
None
} else {
let mut chars = input.chars();
if chars.next().unwrap() == target {
Some(chars.as_str())
} else {
None
}
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
if !input.contains(target) {
(input, "")
} else {
let (first, last) = input.split_at(input.find(target).unwrap());
(first, last)
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
if !input.contains(target) {
None
} else {
let (first, last) = input.split_at(input.find(target).unwrap());
let mut last_chars = last.chars();
last_chars.next();
Some((first, last_chars.as_str()))
}
}
#[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>>>,
}
use std::io::Write;
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut buf = String::new();
let num_bytes = reader.read_line(&mut buf);
match num_bytes {
Ok(0) => return Err(CsvError::InvalidHeader(buf)),
Ok(_) => (),
Err(e) => return Err(CsvError::IO(e)),
};
let header: Vec<String> = buf.split(',').map(|x| x.trim().to_string()).collect();
for element in 0..header.len() {
for element2 in (element + 1)..header.len() {
if header[element] == header[element2] {
return Err(CsvError::InvalidHeader(buf));
}
}
}
Ok(Csv {
columns: header,
reader: reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut parsed_line: Row = Row::new();
let mut parsed_words: Vec<String> = Vec::new();
let mut line_for_parse = line;
while !line_for_parse.is_empty() {
let mut rest_of_line = line_for_parse.trim();
rest_of_line = match skip_next(rest_of_line, '"') {
Some(text) => text,
None => return Err(CsvError::InvalidRow(line.to_string())),
};
let (word, mut rest_of_line) = match take_and_skip(rest_of_line, '"') {
Some((word, rest_of_line)) => (word, rest_of_line),
None => return Err(CsvError::InvalidRow(line.to_string())),
};
if !rest_of_line.is_empty() {
rest_of_line = rest_of_line.trim();
rest_of_line = match skip_next(rest_of_line, ',') {
Some(text) => text,
None => return Err(CsvError::InvalidRow(line.to_string())),
};
}
line_for_parse = rest_of_line;
parsed_words.push(word.to_string());
}
if self.columns.len() != parsed_words.len() {
return Err(CsvError::InvalidRow(line.to_string()));
}
for i in 0..parsed_words.len() {
parsed_line.insert(
String::from(&self.columns[i]),
String::from(&parsed_words[i]),
);
}
Ok(parsed_line)
}
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> {
for i in 0..self.columns.len() {
match writer.write(self.columns[i].as_bytes()) {
Err(e) => return Err(CsvError::IO(e)),
Ok(_) => (),
}
if i < self.columns.len() - 1 {
match writer.write(", ".as_bytes()) {
Err(e) => return Err(CsvError::IO(e)),
Ok(_) => (),
}
}
}
while let Some(row) = self.next() {
match writer.write("\n".as_bytes()) {
Err(e) => return Err(CsvError::IO(e)),
Ok(_) => (),
}
let current_row: Row = row.unwrap();
for i in 0..self.columns.len() {
let mut line = String::new();
line.push('\"');
match current_row.get(&self.columns[i]) {
Some(data) => line.push_str(data),
None => (),
}
line.push('\"');
if i < self.columns.len() - 1 {
line.push_str(", ");
}
match writer.write(line.as_bytes()) {
Err(e) => return Err(CsvError::IO(e)),
Ok(_) => (),
}
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut buf: String = String::new();
let num_bytes = self.reader.read_line(&mut buf);
match num_bytes {
Ok(0) => return None,
Ok(_) => (),
Err(e) => return Some(Err(CsvError::IO(e))),
}
let current_row: Row;
current_row = match self.parse_line(&buf) {
Ok(row) => row,
Err(e) => return Some(Err(e)),
};
match &self.selection {
None => return Some(Ok(current_row)),
Some(function) => match function(&current_row) {
Err(e) => return Some(Err(e)),
Ok(false) => (),
Ok(true) => return Some(Ok(current_row)),
},
}
}
}
}

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

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

running 15 tests
test solution_test::test_csv_basic ... ok
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 ... 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 ... 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

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

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

Георги качи първо решение на 10.01.2021 18:26 (преди над 4 години)