Решение на CSV Filter от Иван Лучев

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

Към профила на Иван Лучев

Резултати

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

Код

use std::collections::{HashMap, HashSet};
type Row = HashMap<String, String>;
use std::io::BufRead;
use std::io::Write;
#[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>>>,
}
fn sizeof_char(c: char) -> usize {
String::from(c).len()
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input.chars().next() == Some(target) {
let start_byte_of_second_char = sizeof_char(input.chars().next().unwrap());
Some(&input[start_byte_of_second_char..])
} else {
None
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let first_half_end = input.chars().take_while(|x| x != &target).fold(0, |acc, x| acc + sizeof_char(x));
(&input[0..first_half_end], &input[first_half_end..])
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (left_half, right_half) = take_until(input, target);
if let Some(right_half) = skip_next(right_half, target) {
Some((left_half, right_half))
} else {
None
}
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_buffer = String::new();
match reader.read_line(&mut header_buffer) {
Ok(0) => return Err(CsvError::InvalidHeader("Empty header line".to_string())),
Err(x) => return Err(CsvError::IO(x)),
_ => (),
}
let columns: Vec<String> = header_buffer.split(',').map(|x| x.trim()).map(|x| x.to_string()).collect();
if columns.len() != columns.iter().cloned().collect::<HashSet<_>>().len() {
return Err(CsvError::InvalidHeader("Dublicate column names".to_string()));
}
Ok(Csv {
columns,
reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut line_remains = line.trim();
let mut row = Row::new();
for (index, column) in self.columns.iter().enumerate() {
if index > 0 {
match take_and_skip(line_remains, ',') {
Some((_, new_remains)) => line_remains = new_remains,
None => return Err(CsvError::InvalidRow(format!("No delimiting comma between values #{} and #{}", index - 1, index))),
}
}
if let Some((empty_prefix, new_remains)) = take_and_skip(line_remains, '"') {
if empty_prefix.trim() != "" {
return Err(CsvError::InvalidRow(format!("Found garbage between CSV values #{} and #{}", index, index + 1)))
}
line_remains = new_remains;
} else {
return Err(CsvError::InvalidRow(format!("Value #{} is missing an opening \"", index)));
}
if let Some((value, new_remains)) = take_and_skip(line_remains, '"') {
row.insert(column.clone(), value.to_string());
line_remains = new_remains;
} else {
return Err(CsvError::InvalidRow(format!("Value #{} is missing a closing \"", index)));
}
}
if line_remains.trim() != ""{
return Err(CsvError::InvalidRow("Row has too many values".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> {
write!(writer, "{}", self.columns.join(", "))?;
while let Some(row) = self.next() {
let row_map = row?;
let row_string: Vec<String> = self.columns.iter().map(|x| "\"".to_string() + row_map.get(x).unwrap() + "\"").collect();
write!(writer, "\n{}", row_string.join(", "))?;
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buffer = String::new();
match self.reader.read_line(&mut buffer) {
Ok(0) => return None,
Err(x) => return Some(Err(CsvError::IO(x))),
_ => (),
}
let row = match self.parse_line(&buffer) {
Ok(x) => x,
Err(x) => return Some(Err(x)),
};
if let Some(f) = self.selection.as_ref() {
match f(&row) {
Err(x) => return Some(Err(x)),
Ok(false) => self.next(),
Ok(true) => Some(Ok(row)),
}
} else {
Some(Ok(row))
}
}
}

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

Compiling solution v0.1.0 (/tmp/d20210111-1538662-zkszn4/solution)
    Finished test [unoptimized + debuginfo] target(s) in 4.29s
     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

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

Иван качи първо решение на 22.12.2020 01:06 (преди почти 5 години)

Иван качи решение на 22.12.2020 13:58 (преди почти 5 години)

use std::collections::{HashMap, HashSet};
type Row = HashMap<String, String>;
use std::io::BufRead;
use std::io::Write;
#[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>>>,
}
fn sizeof_char(c: char) -> usize {
String::from(c).len()
}
+impl From<std::io::Error> for CsvError {
+ fn from(error: std::io::Error) -> Self {
+ CsvError::IO(error)
+ }
+}
+
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input.chars().next() == Some(target) {
let start_byte_of_second_char = sizeof_char(input.chars().next().unwrap());
Some(&input[start_byte_of_second_char..])
} else {
None
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
- let mut first_half_end = 0;
- for ch in input.chars() {
- if ch == target {
- break;
- }
- first_half_end += sizeof_char(ch);
- }
-
+ let first_half_end = input.chars().take_while(|x| x != &target).fold(0, |acc, x| acc + sizeof_char(x));
+
(&input[0..first_half_end], &input[first_half_end..])
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (left_half, right_half) = take_until(input, target);
if let Some(right_half) = skip_next(right_half, target) {
Some((left_half, right_half))
} else {
None
}
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_buffer = String::new();
match reader.read_line(&mut header_buffer) {
Ok(0) => return Err(CsvError::InvalidHeader("Empty header line".to_string())),
Err(x) => return Err(CsvError::IO(x)),
_ => (),
}
let columns: Vec<String> = header_buffer.split(',').map(|x| x.trim()).map(|x| x.to_string()).collect();
if columns.len() != columns.iter().cloned().collect::<HashSet<_>>().len() {
return Err(CsvError::InvalidHeader("Dublicate column names".to_string()));
}
Ok(Csv {
columns,
reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut line_remains = line.trim();
let mut row = Row::new();
- for i in 0..self.columns.len() {
- if i > 0 {
+ for (index, column) in self.columns.iter().enumerate() {
+ if index > 0 {
match take_and_skip(line_remains, ',') {
- Some((_, x)) => line_remains = x,
- None => return Err(CsvError::InvalidRow(format!("No delimiting , between values #{} and #{}", i - 1, i))),
+ Some((_, new_remains)) => line_remains = new_remains,
+ None => return Err(CsvError::InvalidRow(format!("No delimiting comma between values #{} and #{}", index - 1, index))),
}
}
- match take_and_skip(line_remains, '"') {
- Some((empty_prefix, x)) => {
- if empty_prefix.trim() != "" {
- return Err(CsvError::InvalidRow(format!("Found garbage between CSV values #{} and #{}", i, i + 1)))
- }
- line_remains = x
- },
- None => return Err(CsvError::InvalidRow(format!("Value #{} is missing an opening \"", i))),
+ if let Some((empty_prefix, new_remains)) = take_and_skip(line_remains, '"') {
+ if empty_prefix.trim() != "" {
+ return Err(CsvError::InvalidRow(format!("Found garbage between CSV values #{} and #{}", index, index + 1)))
+ }
+ line_remains = new_remains;
+ } else {
+ return Err(CsvError::InvalidRow(format!("Value #{} is missing an opening \"", index)));
}
- if let Some((value, remains)) = take_and_skip(line_remains, '"') {
- row.insert(self.columns[i].clone(), value.to_string());
- line_remains = remains;
+ if let Some((value, new_remains)) = take_and_skip(line_remains, '"') {
+ row.insert(column.clone(), value.to_string());
+ line_remains = new_remains;
} else {
- return Err(CsvError::InvalidRow(format!("Value #{} is missing a closing \"", i)));
+ return Err(CsvError::InvalidRow(format!("Value #{} is missing a closing \"", index)));
}
}
if line_remains.trim() != ""{
return Err(CsvError::InvalidRow("Row has too many values".to_string()))
}
Ok(row)
}
pub fn apply_selection<F>(&mut self, callback: F)
- where
- F: Fn(&Row) -> Result<bool, CsvError> + 'static,
- {
+ 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> {
- match write!(writer, "{}", self.columns.join(", ")) {
- Err(x) => return Err(CsvError::IO(x)),
- Ok(_) => (),
- }
-
+ write!(writer, "{}", self.columns.join(", "))?;
+
while let Some(row) = self.next() {
let row_map = row?;
let row_string: Vec<String> = self.columns.iter().map(|x| "\"".to_string() + row_map.get(x).unwrap() + "\"").collect();
- match write!(writer, "\n{}", row_string.join(", ")) {
- Err(x) => return Err(CsvError::IO(x)),
- Ok(_) => (),
- }
+ write!(writer, "\n{}", row_string.join(", "))?;
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buffer = String::new();
match self.reader.read_line(&mut buffer) {
Ok(0) => return None,
Err(x) => return Some(Err(CsvError::IO(x))),
_ => (),
}
let row = match self.parse_line(&buffer) {
Ok(x) => x,
Err(x) => return Some(Err(x)),
};
if let Some(f) = self.selection.as_ref() {
match f(&row) {
Err(x) => return Some(Err(x)),
Ok(false) => self.next(),
Ok(true) => Some(Ok(row)),
}
} else {
Some(Ok(row))
}
}
}