Решение на CSV Filter от Костадин Пеков

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

Към профила на Костадин Пеков

Резултати

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

Код

#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut chars = input.chars();
if chars.next() == Some(target)
{
let (first, last) = input.split_at(1);
return Some(last);
}
None
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let mut chars = input.chars();
let mut count = 0;
for i in 0..input.chars().count()
{
if chars.next() == Some(target)
{
return input.split_at(i);
}
//i += 1;
}
return (input,"");
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (first, last) = take_until(input, target);
if skip_next(last,target) == None
{
return None;
}
Some((first,skip_next(last,target).unwrap()))
}
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 From<std::io::Error> for CsvError {
fn from(err: std::io::Error) -> Self {
CsvError::IO(err)
}
}
impl<R: BufRead> Csv<R> {
/// Конструира нова стойност от подадения вход. Третира се като "нещо, от което може да се чете
/// ред по ред".
///
/// Очакваме да прочетете първия ред от входа и да го обработите като заглавна част ("header").
/// Това означава, че първия ред би трябвало да включва имена на колони, разделени със
/// запетайки и може би празни места. Примерно:
///
/// - name, age
/// - name,age,birth date
///
/// В случай, че има грешка от викане на методи на `reader`, тя би трябвало да е `io::Error`.
/// върнете `CsvError::IO`, което опакова въпросната грешка.
///
/// Ако първия ред е празен, прочитането ще ви върне 0 байта. Примерно, `read_line` връща
/// `Ok(0)` в такъв случай. Това означава, че нямаме валиден header -- нито една колона няма,
/// очакваме грешка `CsvError::InvalidHeader`.
///
/// Ако има дублиране на колони -- две колони с едно и също име -- също върнете
/// `CsvError::InvalidHeader`.
///
/// Ако всичко е наред, върнете конструирана стойност, на която `columns` е списък с колоните,
/// в същия ред, в който са подадени, без заобикалящите ги празни символи (използвайте
/// `.trim()`).
///
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut buf = String::new();
let mut num_lines = reader.read_line(&mut buf)?;
//let result: Result<usize, Error> = try {
// reader.read_line(&mut buf)?;
//};
//if let Err(e) = result()
//{
// return Err(CsvError::IO(_));
//}
if num_lines == 0
{
return Err(CsvError::InvalidHeader("nothing to read".to_string()));
}
let mut columns_result = Vec::<String>::new();
//let mut current_buf = String::new();
while buf != ""
{
let mut buf_cur = String::new();
match take_and_skip(&buf,',')
{
Some((first,second)) => {
buf_cur = second.to_string();
columns_result.push(first.trim().to_string());
},
None => columns_result.push(buf.trim().to_string()),
};
buf = buf_cur;
}
for i in 0..columns_result.len()
{
for j in 0..columns_result.len()
{
if i != j && columns_result[i] == columns_result[j]
{
return Err(CsvError::InvalidHeader("duplicating columns".to_string()));
}
}
}
Ok(Csv{columns : columns_result, reader: reader, selection: None})
}
/// Функцията приема следващия ред за обработка и конструира `Row` стойност
/// (`HashMap<String, String>`) със колоните и съответсващите им стойности на този ред.
///
/// Алгоритъма е горе-долу:
///
/// 1. Изчистете реда с `.trim()`.
/// 2. Очаквате, че реда ще започне със `"`, иначе връщате грешка.
/// 3. Прочитате съдържанието от отварящата кавичка до следващата. Това е съдържанието на
/// стойността на текущата колона на този ред. Не го чистите от whitespace, просто го
/// приемате както е.
/// 4. Ако не намерите затваряща кавичка, това е грешка.
/// 5. Запазвате си стойността в един `Row` (`HashMap`) -- ключа е името на текущата колона,
/// до която сте стигнали, стойността е това, което току-що изпарсихте.
/// 6. Ако нямате оставащи колони за обработка и нямате оставащо съдържание от реда, всичко
/// е ок. Връщате реда.
/// 7. Ако нямате оставащи колони, но имате още от реда, или обратното, това е грешка.
///
/// За този процес, помощните функции, които дефинирахме по-горе може да ви свършат работа.
/// *Може* да използвате вместо тях `.split` по запетайки, но ще имаме поне няколко теста със
/// вложени запетайки. Бихте могли и с това да се справите иначе, разбира се -- ваш избор.
///
/// Внимавайте с празното пространство преди и след запетайки -- викайте `.trim()` на ключови
/// места. Всичко в кавички се взема както е, всичко извън тях се чисти от whitespace.
///
/// Всички грешки, които ще връщате, се очаква да бъдат `CsvError::InvalidRow`.
///
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
//let mut buf = String::new();
//let mut num_lines = self.reader.read_line(&mut buf);
let mut holder = String::new();
let mut line_to_be_processed = line.to_string();
line_to_be_processed = line_to_be_processed.trim().to_string();
let mut found_cols = Row::new();
for i in &self.columns
{
match skip_next(&line_to_be_processed,'"')
{
Some(last) => {
holder = last.to_string();
},
None =>
{
//println!("{}",line_to_be_processed);
return Err(CsvError::InvalidRow("yooo".to_string()))
},
};
line_to_be_processed = holder;
match take_and_skip(&line_to_be_processed,'"')
{
Some((first,second)) =>
{
holder = second.to_string();
found_cols.insert(String::from(i), String::from(first));
}
None => return Err(CsvError::InvalidRow("yeeee".to_string())),
};
line_to_be_processed = holder;
line_to_be_processed = line_to_be_processed.trim().to_string();
match skip_next(&line_to_be_processed,',')
{
Some(last) => {
holder = last.to_string();
},
None => break,
//None => return Err(CsvError::InvalidRow("".to_string())),
};
line_to_be_processed = holder;
line_to_be_processed = line_to_be_processed.trim().to_string();
}
for i in &self.columns
{
if found_cols.contains_key(i) == false
{
return Err(CsvError::InvalidRow("missing key in Row".to_string()));
}
}
Ok(found_cols)
}
/// Подадената функция се очаква да се използва за филтриране -- при итерация, само редове, за
/// които се връща `true` се очаква да се извадят.
///
pub fn apply_selection<F>(&mut self, callback: F)
where F: Fn(&Row) -> Result<bool, CsvError> + 'static
{
self.selection = Some(Box::new(callback));
//selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
/// Извикването на този метод консумира CSV-то и записва филтрираното съдържание в подадената
/// `Write` стойност. Вижте по-долу за пример и детайли.
///
/// Грешките, които се връщат са грешките, които идват от използваните други методи, плюс
/// грешките от писане във `writer`-а, опаковани в `CsvError::IO`.
///
/// В зависимост от това как си имплементирате метода, `mut` може би няма да ви трябва за
/// `self` -- ако имате warning-и, просто го махнете.
///
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
for i in 0..self.columns.len() - 1
{
writer.write(self.columns[i].as_bytes());
writer.write(b", ");
}
writer.write(self.columns[self.columns.len() - 1].as_bytes());
writer.write(b"\n");
//Ok(())
let mut current = self.next();
while !current.is_none()
{
match current{
Some(Err(e))=> return Err(e),
None => return Ok(()),
row_found =>
{
for i in 0..self.columns.len() - 1
{
writer.write(b"\"");
writer.write(row_found.as_ref().unwrap().as_ref().unwrap()[&self.columns[i]].as_bytes());
writer.write(b"\", ");
}
writer.write(b"\"");
writer.write(row_found.as_ref().unwrap().as_ref().unwrap()[&self.columns[self.columns.len() - 1]].as_bytes());
writer.write(b"\"\n");
}
}
current = self.next();
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
/// Итерацията се състои от няколко стъпки:
///
/// 1. Прочитаме следващия ред от входа:
/// -> Ако има грешка при четене, връщаме Some(CsvError::IO(...))
/// -> Ако успешно се прочетат 0 байта, значи сме на края на входа, и няма какво повече да
/// четем -- връщаме `None`
/// -> Иначе, имаме успешно прочетен ред, продължаваме напред
/// 2. Опитваме се да обработим прочетения ред със `parse_line`:
/// -> Ако има грешка при парсене, връщаме Some(CsvError-а, който се връща от `parse_line`)
/// -> Ако успешно извикаме `parse_line`, вече имаме `Row` стойност.
/// 3. Проверяваме дали този ред изпълнява условието, инсталирано чрез `apply_selection`:
/// -> Ако функцията върне грешка, връщаме я опакована във `Some`.
/// -> Ако функцията върне Ok(false), *не* връщаме този ред, а пробваме следващия (обратно
/// към стъпка 1)
/// -> При Ok(true), връщаме този ред, опакован във `Some`
///
/// Да, тази функция връща `Option<Result<...>>` :). `Option` защото може да има, може да няма
/// следващ ред, `Result` защото четенето на реда (от примерно файл) може да не сработи.
///
fn next(&mut self) -> Option<Self::Item> {
let mut buf = String::new();
let mut num_lines = self.reader.read_line(&mut buf).ok()?;
let mut cols = Row::new();
while num_lines != 0
{
match self.parse_line(&buf)
{
Err(e) => return Some(Err(e)),
found_cols => cols = found_cols.unwrap(),
}
match &self.selection
{
None=> return Some(Ok(cols)),
Some(f) =>
{
match f.as_ref()(&cols)
{
Err(e) => return Some(Err(e)),
Ok(true) => return Some(Ok(cols)),
Ok(false) => num_lines = self.reader.read_line(&mut buf).ok()?,
}
}
}
//match self.selection.as_ref().unwrap()(&cols)
//{
// Err(e) => return Some(Err(e)),
// Ok(true) => return Some(Ok(cols)),
// Ok(false) => num_lines = self.reader.read_line(&mut buf).ok()?,
//}
//}
//else
//{
// return Some(Ok(cols));
//}
}
//println!("here:{}", self.selection.as_ref().unwrap()(&cols).unwrap());
return None;
}
}
use std::io::{self, Read, BufReader};
// За тестване че някакъв резултат пасва на някакъв pattern:
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
if let $pat = $expr {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", $expr, stringify!($pat));
}
}
}
// За тестване на IO грешки:
struct ErroringReader {}
impl Read for ErroringReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "read error!"))
}
}
impl BufRead for ErroringReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
Err(io::Error::new(io::ErrorKind::Other, "fill_buf error!"))
}
fn consume(&mut self, _amt: usize) { }
}

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

Compiling solution v0.1.0 (/tmp/d20210111-1538662-gcpwqo/solution)
warning: unused import: `BufReader`
   --> src/lib.rs:329:27
    |
329 | use std::io::{self, Read, BufReader};
    |                           ^^^^^^^^^
    |
    = note: `#[warn(unused_imports)]` on by default

warning: unused macro definition
   --> src/lib.rs:332:1
    |
332 | / macro_rules! assert_match {
333 | |     ($expr:expr, $pat:pat) => {
334 | |         if let $pat = $expr {
335 | |             // all good
...   |
339 | |     }
340 | | }
    | |_^
    |
    = note: `#[warn(unused_macros)]` on by default

warning: unused variable: `first`
  --> src/lib.rs:15:14
   |
15 |         let (first, last) = input.split_at(1);
   |              ^^^^^ help: if this is intentional, prefix it with an underscore: `_first`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `count`
  --> src/lib.rs:23:9
   |
23 |     let mut count = 0;
   |         ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_count`

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

warning: value assigned to `cols` is never read
   --> src/lib.rs:287:13
    |
287 |         let mut cols = Row::new();
    |             ^^^^^^^^
    |
    = help: maybe it is overwritten before being read?

warning: variable does not need to be mutable
  --> src/lib.rs:23:9
   |
23 |     let mut count = 0;
   |         ----^^^^^
   |         |
   |         help: remove this `mut`
   |
   = note: `#[warn(unused_mut)]` on by default

warning: variable does not need to be mutable
  --> src/lib.rs:87:13
   |
87 |         let mut num_lines = reader.read_line(&mut buf)?;
   |             ----^^^^^^^^^
   |             |
   |             help: remove this `mut`

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:230:13
    |
230 |             writer.write(self.columns[i].as_bytes());
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: `#[warn(unused_must_use)]` on by default
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:231:13
    |
231 |             writer.write(b", ");
    |             ^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:233:9
    |
233 |         writer.write(self.columns[self.columns.len() - 1].as_bytes());
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:234:9
    |
234 |         writer.write(b"\n");
    |         ^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:246:29
    |
246 | ...                   writer.write(b"\"");
    |                       ^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:247:29
    |
247 | ...                   writer.write(row_found.as_ref().unwrap().as_ref().unwrap()[&self.columns[i]].as_bytes());
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:248:29
    |
248 | ...                   writer.write(b"\", ");
    |                       ^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:250:21
    |
250 |                     writer.write(b"\"");
    |                     ^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:251:21
    |
251 |                     writer.write(row_found.as_ref().unwrap().as_ref().unwrap()[&self.columns[self.columns.len() - 1]].as_bytes());
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: unused `std::result::Result` that must be used
   --> src/lib.rs:252:21
    |
252 |                     writer.write(b"\"\n");
    |                     ^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: this `Result` may be an `Err` variant, which should be handled

warning: 18 warnings emitted

    Finished test [unoptimized + debuginfo] target(s) in 4.03s
     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 ... FAILED
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 ... FAILED
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 ... 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_iterating_with_a_selection stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `["Douglas Adams"]`,
 right: `["Douglas Adams", "Ada Lovelace"]`', tests/solution_test.rs:219:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- solution_test::test_csv_selection_and_writing stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date"]`,
 right: `["name, age, birth date", "\"Gen Z. Person\", \"20\", \"2000-01-01\""]`', tests/solution_test.rs:269:9
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date"]`,
 right: `["name, age, birth date", "\"Gen Z. Person\", \"20\", \"2000-01-01\""]`', tests/solution_test.rs:251: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 `↓яга`', /rustc/e1884a8e3c3e813aada8254edfa120e85bf5ffca/library/core/src/str/mod.rs:565:13

---- solution_test::test_take_and_skip stdout ----
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `None`,
 right: `Some(("баба", "яга"))`', tests/solution_test.rs:128:5

---- 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_iterating_with_a_selection
    solution_test::test_csv_selection_and_writing
    solution_test::test_parsing_helpers_for_unicode
    solution_test::test_take_and_skip
    solution_test::test_take_until

test result: FAILED. 10 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out

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

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

Костадин качи първо решение на 10.01.2021 18:07 (преди над 4 години)