Решение на Logging от Димитър Михайлов

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

Към профила на Димитър Михайлов

Резултати

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

Код

use std::time::Instant;
use std::time::Duration;
use std::io::Write;
use std::io;
use std::rc::Rc;
use std::str;
use std::cell::RefCell;
use std::string::String;
pub trait Logger {
/// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
/// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
/// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
/// логиката.
///
/// Втория аргумент е низа, който ще се логва.
///
fn push(&mut self, time: Instant, text: &str);
/// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
/// да вземе текущ timestamp.
///
fn log(&mut self, text: &str);
/// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
/// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
/// се имплементира като просто `Ok(())`.
///
fn try_flush(&mut self) -> io::Result<()>;
/// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
/// Error handling-а, който очакваме.
///
fn flush(&mut self);
}
pub struct BufferedLogger<W: Write> {
buffer_size: usize,
buffer: Rc<RefCell<Vec<(String, Instant)>>>,
out: Rc<RefCell<W>>
}
impl<W: Write> BufferedLogger<W> {
/// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
/// в подадената структура от тип, който имплементира `Write`;
///
pub fn new(out: W, buffer_size: usize) -> Self {
return BufferedLogger { out: Rc::new(RefCell::new(out)), buffer_size, buffer: Rc::new(RefCell::new(Vec::new())) }
}
/// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
/// подредени по времето, в което са log-нати, от най-ранни до най-късни.
///
pub fn buffered_entries(&self) -> Vec<String> {
let mut buffer = self.buffer.borrow_mut().clone();
buffer.sort_by(|(_, time_a), (_, time_b)| time_a.cmp(time_b));
let iter = buffer.into_iter();
let mut sorted_str = Vec::new();
for (str, _time) in iter {
sorted_str.push(str);
}
return sorted_str
}

Решението ти се компилира в момента, така че внимавай с промените :). Имайки това предвид, помисли си дали сортираш правилно буфера. Прочети инструкцията и си помисли какъв тест можеш да напишеш, за да провериш.

logger.push(now + Duration::from_millis(2), "Test2"); logger.push(now + Duration::from_millis(1), "Test1"); assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);

това не е ли тестът, който проверява сортирането? В случая първо вкарваме Test2 после Test1, но Test1 е преди Test2, гледайки Duration-a. И buffered_entries() ги изкарва в правилния ред, а именно Test1, Test2

Да, това се надявах да осъзнаеш :). Тестовете, които сме ви дали, са доста прости, и са по един тест на казус. Добре е човек да напише поне два теста за нещо, с различни данни, защото единия може да минава на късмет.

Иначе, един начин да избегнеш това е да съхраняваш (time, text) вместо (text, time). Така сортирането ще ги подреди по първия елемент, и само ако първите са едни и същи, ще сортира и вторите азбучно (не сме ви казали какво трябва да се случи ако времената са едни и същи, така че няма значение за решението).

Друг метод е вместо tuple да си направиш собствен тип, примерно Entry, и да му дефинираш Ord. Може да погледнеш моето решение, макар че в него аз съм го имплементирал малко наобратно, понеже използвам BinaryHeap вместо Vec :).

fn check_buffer_size(&mut self) -> bool {
self.buffer_size == self.buffer.borrow_mut().len()
}
}
/// Вижте по-долу за бележки за клонирането
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
return BufferedLogger { out: Rc::clone(&self.out), buffer_size: self.buffer_size, buffer: Rc::clone(&self.buffer)}
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
self.buffer.borrow_mut().push((String::from(text), time));
if self.check_buffer_size() {
self.flush();
}
}
fn log(&mut self, text: &str) {
self.buffer.borrow_mut().push((String::from(text), Instant::now()));
if self.check_buffer_size() {
self.flush();
}
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}

Този метод не беше идеята винаги да връща успех :). Идеята беше той да върши работата и да връща грешка, ако се случи грешка, а flush да я логва и продължава. И сме написали тест, който да проверява това. Може да разгледаш тестовете тук: https://github.com/fmi/rust-homework/blob/master/03/test_full.rs

fn flush(&mut self) {
for str in self.buffered_entries() {
self.out.borrow_mut().write(str.as_bytes()).expect("Could not write");
self.out.borrow_mut().write(b"\n").expect("Could not write");
}
self.buffer = Rc::new(RefCell::new(Vec::new()));
}
}
pub struct MultiLogger {
logger: Vec<Box<dyn Logger>>
}
impl MultiLogger {
pub fn new() -> Self {
return MultiLogger { logger: Vec::new() }
}
pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
self.logger.push(Box::from(logger));
}
}
impl Logger for MultiLogger {
/// Подходящи имплементации на Logger методите
fn push(&mut self, time: Instant, text: &str) {
for logger in self.logger.iter_mut() {
logger.push(time, text);
}
}
fn log(&mut self, text: &str) {
for logger in self.logger.iter_mut() {
logger.log(text);
}
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
for logger in self.logger.iter_mut() {
logger.flush();
}
}
}
pub struct ScopedLogger<L: Logger> {
/// Каквито полета решите, че ви трябват
tag: String,
base_logger: Box<L>
}
impl<L: Logger> ScopedLogger<L> {
pub fn new(tag: &str, base_logger: L) -> Self {
return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
}
}
impl<L: Logger> Logger for ScopedLogger<L> {
/// Подходящи имплементации на Logger методите
fn push(&mut self, time: Instant, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().push(time, c.as_str());
}
fn log(&mut self, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().log(c.as_str())
}

Не би трябвало да е нужно да викаш as_mut() тук, понеже Box-а е "прозрачен" откъм методи -- извикването на метод вика deref или deref_mut когато е нужно и свежда box-а до reference.

Форматирането на низа можеше и да е по-просто като че ли :). И без малко особено-именувани променливи като a, b, и c. Аз съм го направил така:

self.base_logger.push(time, &format!("[{}] {}", self.tag, text));

А метода log няма нужда да повтаря кода. Можеше даже да е имплементиран в самия trait:

fn log(&mut self, text: &str) {
    self.push(Instant::now(), text)
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
self.base_logger.as_mut().flush();
}
}
fn main() {
let mut logger = BufferedLogger::new(io::stdout(), 100);
let now = Instant::now();
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(1), "Test1");
assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
let cloned_logger = logger.clone();
assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
logger.try_flush().unwrap();
logger.flush();
logger.log("Test");
assert_eq!(logger.buffered_entries().len(), 1);
let logger1 = BufferedLogger::new(Vec::new(), 100);
let logger2 = BufferedLogger::new(Vec::new(), 100);
let now = Instant::now();
let mut logger = MultiLogger::new();
logger.log_to(logger1.clone());
logger.push(now + Duration::from_millis(1), "Test1");
logger.log_to(ScopedLogger::new("Second", logger2.clone()));
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(3), "Test3");
assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
logger.try_flush().unwrap();
logger.flush();
let base = BufferedLogger::new(Vec::new(), 100);
let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
logger.log("Test");
assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

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

Compiling solution v0.1.0 (/tmp/d20190123-22631-14ux4a8/solution)
warning: function is never used: `main`
   --> src/lib.rs:185:1
    |
185 | fn main() {
    | ^^^^^^^^^
    |
    = note: #[warn(dead_code)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 4.94s
     Running target/debug/deps/solution-2e785d603b538f71

running 0 tests

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

     Running target/debug/deps/solution_test-29808948fb50ed3a

running 15 tests
test solution_test::test_automatic_flushing_when_buffer_limit_is_reached ... ok
test solution_test::test_automatic_flushing_when_zero_buffer_limit ... FAILED
test solution_test::test_basic_log ... ok
test solution_test::test_basic_push ... ok
test solution_test::test_cloning_a_logger_shares_a_buffer ... ok
test solution_test::test_cloning_a_logger_shares_their_io ... ok
test solution_test::test_erroring_io ... FAILED
test solution_test::test_flushing_the_buffer ... ok
test solution_test::test_logger_combinations ... ok
test solution_test::test_multilogger_logs_and_flushes_when_needed ... FAILED
test solution_test::test_multilogger_logs_to_several_ios ... ok
test solution_test::test_reordering_logs_in_buffer ... ok
test solution_test::test_reordering_logs_in_io ... ok
test solution_test::test_scoped_logger ... FAILED
test solution_test::test_scoped_logger_with_a_string_tag ... FAILED

failures:

---- solution_test::test_automatic_flushing_when_zero_buffer_limit stdout ----
thread 'solution_test::test_automatic_flushing_when_zero_buffer_limit' panicked at 'assertion failed: `(left == right)`
  left: `1`,
 right: `0`', tests/solution_test.rs:210:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.

---- solution_test::test_erroring_io stdout ----
thread 'solution_test::test_erroring_io' panicked at 'Expected try_flush with an erroring IO to return an error', tests/solution_test.rs:340:9

---- solution_test::test_multilogger_logs_and_flushes_when_needed stdout ----
thread 'solution_test::test_multilogger_logs_and_flushes_when_needed' panicked at 'assertion failed: `(left == right)`
  left: `3`,
 right: `0`', tests/solution_test.rs:288:9

---- solution_test::test_scoped_logger stdout ----
thread 'solution_test::test_scoped_logger' panicked at 'assertion failed: `(left == right)`
  left: `"[First] One\n[Second] Two\n[Second] Three\n[First] Four\n[First] One\n[Second] Two\n[Second] Three\n[First] Four\n"`,
 right: `"[First] One\n[Second] Two\n[Second] Three\n[First] Four\n"`', tests/solution_test.rs:315:5

---- solution_test::test_scoped_logger_with_a_string_tag stdout ----
thread 'solution_test::test_scoped_logger_with_a_string_tag' panicked at 'assertion failed: `(left == right)`
  left: `""`,
 right: `"[First] Test\n"`', tests/solution_test.rs:329:5


failures:
    solution_test::test_automatic_flushing_when_zero_buffer_limit
    solution_test::test_erroring_io
    solution_test::test_multilogger_logs_and_flushes_when_needed
    solution_test::test_scoped_logger
    solution_test::test_scoped_logger_with_a_string_tag

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

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

История (6 версии и 11 коментара)

Димитър обнови решението на 18.12.2018 00:16 (преди над 1 година)

+fn main() {
+
+}

Димитър обнови решението на 18.12.2018 00:18 (преди над 1 година)

fn main() {
+ println!("Hello, world!");
-}
+ // let mut logger = BufferedLogger::new(io::stdout(), 100);
+ // let now = Instant::now();
+
+ // logger.push(now + Duration::from_millis(2), "Test2");
+ // logger.push(now + Duration::from_millis(1), "Test1");
+
+ // assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
+
+ // let cloned_logger = logger.clone();
+ // assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
+
+ // logger.try_flush().unwrap();
+ // logger.flush();
+ // logger.log("Test");
+
+ // assert_eq!(logger.buffered_entries().len(), 1);
+
+ // let logger1 = BufferedLogger::new(Vec::new(), 100);
+ // let logger2 = BufferedLogger::new(Vec::new(), 100);
+ // let now = Instant::now();
+
+ // let mut logger = MultiLogger::new();
+ // logger.log_to(logger1.clone());
+ // logger.push(now + Duration::from_millis(1), "Test1");
+
+ // logger.log_to(ScopedLogger::new("Second", logger2.clone()));
+ // logger.push(now + Duration::from_millis(2), "Test2");
+ // logger.push(now + Duration::from_millis(3), "Test3");
+
+ // assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
+ // assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
+
+ // logger.try_flush().unwrap();
+ // logger.flush();
+
+ // let base = BufferedLogger::new(Vec::new(), 100);
+ // let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
+ // logger.log("Test");
+
+ // assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
+}

Димитър обнови решението на 18.12.2018 00:20 (преди над 1 година)

fn main() {
println!("Hello, world!");
+ // Не знам защо, но не ми дава да го пусна
+ // Ето pastebin, https://pastebin.com/3VuyE6E4
+ // Ако може помощ за имплементациите на функциите на MultiLogger-а, не мога да unborrow-на
// let mut logger = BufferedLogger::new(io::stdout(), 100);
// let now = Instant::now();
// logger.push(now + Duration::from_millis(2), "Test2");
// logger.push(now + Duration::from_millis(1), "Test1");
// assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
// let cloned_logger = logger.clone();
// assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
// logger.try_flush().unwrap();
// logger.flush();
// logger.log("Test");
// assert_eq!(logger.buffered_entries().len(), 1);
// let logger1 = BufferedLogger::new(Vec::new(), 100);
// let logger2 = BufferedLogger::new(Vec::new(), 100);
// let now = Instant::now();
// let mut logger = MultiLogger::new();
// logger.log_to(logger1.clone());
// logger.push(now + Duration::from_millis(1), "Test1");
// logger.log_to(ScopedLogger::new("Second", logger2.clone()));
// logger.push(now + Duration::from_millis(2), "Test2");
// logger.push(now + Duration::from_millis(3), "Test3");
// assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
// assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
// logger.try_flush().unwrap();
// logger.flush();
// let base = BufferedLogger::new(Vec::new(), 100);
// let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
// logger.log("Test");
// assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

Димитър обнови решението на 18.12.2018 00:21 (преди над 1 година)

+use std::time::Instant;
+use std::time::Duration;
+use std::io::Write;
+use std::io;
+use std::rc::Rc;
+use std::str;
+use std::cell::RefCell;
+use std::string::String;
+use std::ops::Add;
+
+pub trait Logger {
+ /// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
+ /// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
+ /// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
+ /// логиката.
+ ///
+ /// Втория аргумент е низа, който ще се логва.
+ ///
+ fn push(&mut self, time: Instant, text: &str);
+
+ /// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
+ /// да вземе текущ timestamp.
+ ///
+ fn log(&mut self, text: &str);
+
+ /// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
+ /// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
+ /// се имплементира като просто `Ok(())`.
+ ///
+ fn try_flush(&mut self) -> io::Result<()>;
+
+ /// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
+ /// Error handling-а, който очакваме.
+ ///
+ fn flush(&mut self);
+}
+
+pub struct BufferedLogger<W: Write> {
+ buffer_size: usize,
+ buffer: Rc<RefCell<Vec<(String, Instant)>>>,
+ out: Rc<RefCell<W>>
+}
+
+impl<W: Write> BufferedLogger<W> {
+ /// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
+ /// в подадената структура от тип, който имплементира `Write`;
+ ///
+ pub fn new(out: W, buffer_size: usize) -> Self {
+ return BufferedLogger { out: Rc::new(RefCell::new(out)), buffer_size, buffer: Rc::new(RefCell::new(Vec::new())) }
+ }
+
+ /// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
+ /// подредени по времето, в което са log-нати, от най-ранни до най-късни.
+ ///
+ pub fn buffered_entries(&self) -> Vec<String> {
+ let mut buffer = self.buffer.borrow_mut().clone();
+ buffer.sort();
+ let iter = buffer.into_iter();
+ let mut sorted_str = Vec::new();
+ for (str, _time) in iter {
+ sorted_str.push(str);
+ }
+
+ return sorted_str
+ }
+
+ fn check_buffer_size(&mut self) -> bool {
+ self.buffer_size == self.buffer.borrow_mut().len()
+ }
+}
+
+/// Вижте по-долу за бележки за клонирането
+impl<W: Write> Clone for BufferedLogger<W> {
+ fn clone(&self) -> Self {
+ return BufferedLogger { out: self.out.clone(), buffer_size: self.buffer_size, buffer: self.buffer.clone()}
+ }
+}
+
+impl<W: Write> Logger for BufferedLogger<W> {
+ fn push(&mut self, time: Instant, text: &str) {
+ self.buffer.borrow_mut().push((String::from(text), time));
+
+ if self.check_buffer_size() {
+ self.flush();
+ }
+ }
+
+ fn log(&mut self, text: &str) {
+ self.buffer.borrow_mut().push((String::from(text), Instant::now()));
+
+ if self.check_buffer_size() {
+ self.flush();
+ }
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ return Ok(());
+ }
+
+ fn flush(&mut self) {
+ for str in self.buffered_entries() {
+ self.out.borrow_mut().write(str.as_bytes()).expect("Could not write");
+ self.out.borrow_mut().write(b"\n").expect("Could not write");
+ }
+ self.buffer = Rc::new(RefCell::new(Vec::new()));
+ }
+}
+
+// pub struct MultiLogger {
+// logger: Vec<Box<dyn Logger>>
+// }
+
+// impl MultiLogger {
+// pub fn new() -> Self {
+// // return MultiLogger { logger: Rc::new(RefCell::new(Vec::new()))}
+// return MultiLogger { logger: Vec::new() }
+// }
+
+// pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
+// //self.logger.borrow_mut().push(Box::from(logger));
+// self.logger.push(Box::from(logger))
+// }
+// }
+
+// impl Logger for MultiLogger {

Без MultiLogger не ти се компилира домашното с тестовете, така че погрижи се да оправиш проблема, иначе оставаш с 0 точки. Дори да оставиш MultiLogger нещата с unimplemented!() би било по-добре, но най-добре просто дебъгни проблема. Не забравяй, че имаме и базов тест, с който можеш да провериш дали ще ти се компилира решението поне, виж новината: https://fmi.rust-lang.bg/announcements/5

Иначе, погледни внимателно дефиницията на into_iter и помисли какво прави: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html#tymethod.into_iter. Помисли си и какво значи грешката "cannot move out of borrowed content". Кое е "borrowed", и защо нещо се опитва да "move"-не от него?

+// /// Подходящи имплементации на Logger методите
+// fn push(&mut self, time: Instant, text: &str) {
+// for mut logger in self.logger.into_iter() {
+// logger.push(time, text);
+// }
+// }
+
+// fn log(&mut self, text: &str) {
+// for mut logger in self.logger.into_iter() {
+// logger.log(text);
+// }
+// }
+
+// fn try_flush(&mut self) -> io::Result<()> {
+// //self.logger.as_mut().unwrap().as_mut().try_flush()
+// return Ok(());
+// }
+
+// fn flush(&mut self) {
+// //self.logger.as_mut().unwrap().as_mut().flush()
+// for mut logger in self.logger.into_iter() {
+// logger.flush();
+// }
+// }
+
+// }
+
+pub struct ScopedLogger<L: Logger> {
+ /// Каквито полета решите, че ви трябват
+ tag: String,
+ base_logger: Box<L>
+}
+
+impl<L: Logger> ScopedLogger<L> {
+ pub fn new(tag: &str, base_logger: L) -> Self {
+ return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
+ }
+}
+
+impl<L: Logger> Logger for ScopedLogger<L> {
+ /// Подходящи имплементации на Logger методите
+ fn push(&mut self, time: Instant, text: &str) {
+ let a : String = "[".to_owned();
+ let b : &str = "] ";
+ let c = a + self.tag.as_str() + b + text;
+ self.base_logger.as_mut().push(time, c.as_str());
+ }
+
+ fn log(&mut self, text: &str) {
+ let a : String = "[".to_owned();
+ let b : &str = "] ";
+ let c = a + self.tag.as_str() + b + text;
+ self.base_logger.as_mut().log(c.as_str())
+ }
+
+ fn try_flush(&mut self) -> io::Result<()> {
+ return Ok(());
+ }
+
+ fn flush(&mut self) {
+ self.base_logger.as_mut().flush();
+ }
+}
+
fn main() {
println!("Hello, world!");
- // Не знам защо, но не ми дава да го пусна
- // Ето pastebin, https://pastebin.com/3VuyE6E4
- // Ако може помощ за имплементациите на функциите на MultiLogger-а, не мога да unborrow-на
// let mut logger = BufferedLogger::new(io::stdout(), 100);
// let now = Instant::now();
// logger.push(now + Duration::from_millis(2), "Test2");
// logger.push(now + Duration::from_millis(1), "Test1");
// assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
// let cloned_logger = logger.clone();
// assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
// logger.try_flush().unwrap();
// logger.flush();
// logger.log("Test");
// assert_eq!(logger.buffered_entries().len(), 1);
// let logger1 = BufferedLogger::new(Vec::new(), 100);
// let logger2 = BufferedLogger::new(Vec::new(), 100);
// let now = Instant::now();
// let mut logger = MultiLogger::new();
// logger.log_to(logger1.clone());
// logger.push(now + Duration::from_millis(1), "Test1");
// logger.log_to(ScopedLogger::new("Second", logger2.clone()));
// logger.push(now + Duration::from_millis(2), "Test2");
// logger.push(now + Duration::from_millis(3), "Test3");
// assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
// assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
// logger.try_flush().unwrap();
// logger.flush();
// let base = BufferedLogger::new(Vec::new(), 100);
// let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
// logger.log("Test");
// assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

Димитър обнови решението на 18.12.2018 10:59 (преди над 1 година)

use std::time::Instant;
use std::time::Duration;
use std::io::Write;
use std::io;
use std::rc::Rc;
use std::str;
use std::cell::RefCell;
use std::string::String;
-use std::ops::Add;
pub trait Logger {
/// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
/// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
/// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
/// логиката.
///
/// Втория аргумент е низа, който ще се логва.
///
fn push(&mut self, time: Instant, text: &str);
/// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
/// да вземе текущ timestamp.
///
fn log(&mut self, text: &str);
/// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
/// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
/// се имплементира като просто `Ok(())`.
///
fn try_flush(&mut self) -> io::Result<()>;
/// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
/// Error handling-а, който очакваме.
///
fn flush(&mut self);
}
pub struct BufferedLogger<W: Write> {
buffer_size: usize,
buffer: Rc<RefCell<Vec<(String, Instant)>>>,
out: Rc<RefCell<W>>
}
impl<W: Write> BufferedLogger<W> {
/// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
/// в подадената структура от тип, който имплементира `Write`;
///
pub fn new(out: W, buffer_size: usize) -> Self {
return BufferedLogger { out: Rc::new(RefCell::new(out)), buffer_size, buffer: Rc::new(RefCell::new(Vec::new())) }
}
/// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
/// подредени по времето, в което са log-нати, от най-ранни до най-късни.
///
pub fn buffered_entries(&self) -> Vec<String> {
let mut buffer = self.buffer.borrow_mut().clone();
buffer.sort();
let iter = buffer.into_iter();
let mut sorted_str = Vec::new();
for (str, _time) in iter {
sorted_str.push(str);
}
return sorted_str
}

Решението ти се компилира в момента, така че внимавай с промените :). Имайки това предвид, помисли си дали сортираш правилно буфера. Прочети инструкцията и си помисли какъв тест можеш да напишеш, за да провериш.

logger.push(now + Duration::from_millis(2), "Test2"); logger.push(now + Duration::from_millis(1), "Test1"); assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);

това не е ли тестът, който проверява сортирането? В случая първо вкарваме Test2 после Test1, но Test1 е преди Test2, гледайки Duration-a. И buffered_entries() ги изкарва в правилния ред, а именно Test1, Test2

fn check_buffer_size(&mut self) -> bool {
self.buffer_size == self.buffer.borrow_mut().len()
}
}
/// Вижте по-долу за бележки за клонирането
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
- return BufferedLogger { out: self.out.clone(), buffer_size: self.buffer_size, buffer: self.buffer.clone()}
+ return BufferedLogger { out: Rc::clone(&self.out), buffer_size: self.buffer_size, buffer: Rc::clone(&self.buffer)}
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
self.buffer.borrow_mut().push((String::from(text), time));
if self.check_buffer_size() {
self.flush();
}
}
fn log(&mut self, text: &str) {
self.buffer.borrow_mut().push((String::from(text), Instant::now()));
if self.check_buffer_size() {
self.flush();
}
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
for str in self.buffered_entries() {
self.out.borrow_mut().write(str.as_bytes()).expect("Could not write");
self.out.borrow_mut().write(b"\n").expect("Could not write");
}
self.buffer = Rc::new(RefCell::new(Vec::new()));
}
}
-// pub struct MultiLogger {
-// logger: Vec<Box<dyn Logger>>
-// }
+pub struct MultiLogger {
+ logger: Vec<Box<dyn Logger>>
+}
-// impl MultiLogger {
-// pub fn new() -> Self {
-// // return MultiLogger { logger: Rc::new(RefCell::new(Vec::new()))}
-// return MultiLogger { logger: Vec::new() }
-// }
+impl MultiLogger {
+ pub fn new() -> Self {
+ return MultiLogger { logger: Vec::new() }
+ }
-// pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
-// //self.logger.borrow_mut().push(Box::from(logger));
-// self.logger.push(Box::from(logger))
-// }
-// }
+ pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
+ self.logger.push(Box::from(logger));
+ }
+}
-// impl Logger for MultiLogger {
-// /// Подходящи имплементации на Logger методите
-// fn push(&mut self, time: Instant, text: &str) {
-// for mut logger in self.logger.into_iter() {
-// logger.push(time, text);
-// }
-// }
+impl Logger for MultiLogger {
+ /// Подходящи имплементации на Logger методите
+ fn push(&mut self, time: Instant, text: &str) {
+ for logger in self.logger.iter_mut() {
+ logger.push(time, text);
+ }
+ }
-// fn log(&mut self, text: &str) {
-// for mut logger in self.logger.into_iter() {
-// logger.log(text);
-// }
-// }
+ fn log(&mut self, text: &str) {
+ for logger in self.logger.iter_mut() {
+ logger.log(text);
+ }
+ }
-// fn try_flush(&mut self) -> io::Result<()> {
-// //self.logger.as_mut().unwrap().as_mut().try_flush()
-// return Ok(());
-// }
+ fn try_flush(&mut self) -> io::Result<()> {
+ return Ok(());
+ }
-// fn flush(&mut self) {
-// //self.logger.as_mut().unwrap().as_mut().flush()
-// for mut logger in self.logger.into_iter() {
-// logger.flush();
-// }
-// }
+ fn flush(&mut self) {
+ for logger in self.logger.iter_mut() {
+ logger.flush();
+ }
+ }
-// }
+}
pub struct ScopedLogger<L: Logger> {
/// Каквито полета решите, че ви трябват
tag: String,
base_logger: Box<L>
}
impl<L: Logger> ScopedLogger<L> {
pub fn new(tag: &str, base_logger: L) -> Self {
return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
}
}
impl<L: Logger> Logger for ScopedLogger<L> {
/// Подходящи имплементации на Logger методите
fn push(&mut self, time: Instant, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().push(time, c.as_str());
}
fn log(&mut self, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().log(c.as_str())
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
self.base_logger.as_mut().flush();
}
}
fn main() {
- println!("Hello, world!");
+ let mut logger = BufferedLogger::new(io::stdout(), 100);
+ let now = Instant::now();
- // let mut logger = BufferedLogger::new(io::stdout(), 100);
- // let now = Instant::now();
+ logger.push(now + Duration::from_millis(2), "Test2");
+ logger.push(now + Duration::from_millis(1), "Test1");
- // logger.push(now + Duration::from_millis(2), "Test2");
- // logger.push(now + Duration::from_millis(1), "Test1");
+ assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
- // assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
+ let mut cloned_logger = logger.clone();
+ assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
- // let cloned_logger = logger.clone();
- // assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
+ logger.try_flush().unwrap();
+ logger.flush();
+ logger.log("Test");
- // logger.try_flush().unwrap();
- // logger.flush();
- // logger.log("Test");
+ assert_eq!(logger.buffered_entries().len(), 1);
- // assert_eq!(logger.buffered_entries().len(), 1);
+ let logger1 = BufferedLogger::new(Vec::new(), 100);
+ let logger2 = BufferedLogger::new(Vec::new(), 100);
+ let now = Instant::now();
- // let logger1 = BufferedLogger::new(Vec::new(), 100);
- // let logger2 = BufferedLogger::new(Vec::new(), 100);
- // let now = Instant::now();
+ let mut logger = MultiLogger::new();
+ logger.log_to(logger1.clone());
+ logger.push(now + Duration::from_millis(1), "Test1");
- // let mut logger = MultiLogger::new();
- // logger.log_to(logger1.clone());
- // logger.push(now + Duration::from_millis(1), "Test1");
+ logger.log_to(ScopedLogger::new("Second", logger2.clone()));
+ logger.push(now + Duration::from_millis(2), "Test2");
+ logger.push(now + Duration::from_millis(3), "Test3");
- // logger.log_to(ScopedLogger::new("Second", logger2.clone()));
- // logger.push(now + Duration::from_millis(2), "Test2");
- // logger.push(now + Duration::from_millis(3), "Test3");
+ assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
+ assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
- // assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
- // assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
+ logger.try_flush().unwrap();
+ logger.flush();
- // logger.try_flush().unwrap();
- // logger.flush();
+ let base = BufferedLogger::new(Vec::new(), 100);
+ let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
+ logger.log("Test");
- // let base = BufferedLogger::new(Vec::new(), 100);
- // let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
- // logger.log("Test");
-
- // assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
+ assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

Димитър обнови решението на 18.12.2018 14:08 (преди над 1 година)

use std::time::Instant;
use std::time::Duration;
use std::io::Write;
use std::io;
use std::rc::Rc;
use std::str;
use std::cell::RefCell;
use std::string::String;
pub trait Logger {
/// Метод, който добавя нов запис за логване. Първия аргумент, от тип `std::time::Instant`, е
/// момента във времето, който се асоциира със събитието. Обикновено ще се ползва метода `log`
/// директно, който запълва този параметър, но метода `push` ще е удобен за тестване на
/// логиката.
///
/// Втория аргумент е низа, който ще се логва.
///
fn push(&mut self, time: Instant, text: &str);
/// Метод който ще работи като `push`, с тази разлика, че директно използва `Instant::now()` за
/// да вземе текущ timestamp.
///
fn log(&mut self, text: &str);
/// Метод, който записва нещата от вътрешния буфер към някакъв външен носител -- файл, сокет,
/// стандартния изход. В случай на имплементация, която няма нужда от този метод, винаги може да
/// се имплементира като просто `Ok(())`.
///
fn try_flush(&mut self) -> io::Result<()>;
/// Метод, който прави същото като по-горния, но не връща грешка. Вижте по-долу за бележки за
/// Error handling-а, който очакваме.
///
fn flush(&mut self);
}
pub struct BufferedLogger<W: Write> {
buffer_size: usize,
buffer: Rc<RefCell<Vec<(String, Instant)>>>,
out: Rc<RefCell<W>>
}
impl<W: Write> BufferedLogger<W> {
/// Конструира структура, която ще пази записи в буфер с размер `buffer_size`, и ще ги записва
/// в подадената структура от тип, който имплементира `Write`;
///
pub fn new(out: W, buffer_size: usize) -> Self {
return BufferedLogger { out: Rc::new(RefCell::new(out)), buffer_size, buffer: Rc::new(RefCell::new(Vec::new())) }
}
/// Връща списък от записите, които са буферирани в момента. Записите се очаква да бъдат
/// подредени по времето, в което са log-нати, от най-ранни до най-късни.
///
pub fn buffered_entries(&self) -> Vec<String> {
let mut buffer = self.buffer.borrow_mut().clone();
- buffer.sort();
+ buffer.sort_by(|(_, time_a), (_, time_b)| time_a.cmp(time_b));
let iter = buffer.into_iter();
let mut sorted_str = Vec::new();
for (str, _time) in iter {
sorted_str.push(str);
}
return sorted_str
}

Да, това се надявах да осъзнаеш :). Тестовете, които сме ви дали, са доста прости, и са по един тест на казус. Добре е човек да напише поне два теста за нещо, с различни данни, защото единия може да минава на късмет.

Иначе, един начин да избегнеш това е да съхраняваш (time, text) вместо (text, time). Така сортирането ще ги подреди по първия елемент, и само ако първите са едни и същи, ще сортира и вторите азбучно (не сме ви казали какво трябва да се случи ако времената са едни и същи, така че няма значение за решението).

Друг метод е вместо tuple да си направиш собствен тип, примерно Entry, и да му дефинираш Ord. Може да погледнеш моето решение, макар че в него аз съм го имплементирал малко наобратно, понеже използвам BinaryHeap вместо Vec :).

fn check_buffer_size(&mut self) -> bool {
self.buffer_size == self.buffer.borrow_mut().len()
}
}
/// Вижте по-долу за бележки за клонирането
impl<W: Write> Clone for BufferedLogger<W> {
fn clone(&self) -> Self {
return BufferedLogger { out: Rc::clone(&self.out), buffer_size: self.buffer_size, buffer: Rc::clone(&self.buffer)}
}
}
impl<W: Write> Logger for BufferedLogger<W> {
fn push(&mut self, time: Instant, text: &str) {
self.buffer.borrow_mut().push((String::from(text), time));
if self.check_buffer_size() {
self.flush();
}
}
fn log(&mut self, text: &str) {
self.buffer.borrow_mut().push((String::from(text), Instant::now()));
if self.check_buffer_size() {
self.flush();
}
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}

Този метод не беше идеята винаги да връща успех :). Идеята беше той да върши работата и да връща грешка, ако се случи грешка, а flush да я логва и продължава. И сме написали тест, който да проверява това. Може да разгледаш тестовете тук: https://github.com/fmi/rust-homework/blob/master/03/test_full.rs

fn flush(&mut self) {
for str in self.buffered_entries() {
self.out.borrow_mut().write(str.as_bytes()).expect("Could not write");
self.out.borrow_mut().write(b"\n").expect("Could not write");
}
self.buffer = Rc::new(RefCell::new(Vec::new()));
}
}
pub struct MultiLogger {
logger: Vec<Box<dyn Logger>>
}
impl MultiLogger {
pub fn new() -> Self {
return MultiLogger { logger: Vec::new() }
}
pub fn log_to<L: Logger + 'static>(&mut self, logger: L) {
self.logger.push(Box::from(logger));
}
}
impl Logger for MultiLogger {
/// Подходящи имплементации на Logger методите
fn push(&mut self, time: Instant, text: &str) {
for logger in self.logger.iter_mut() {
logger.push(time, text);
}
}
fn log(&mut self, text: &str) {
for logger in self.logger.iter_mut() {
logger.log(text);
}
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
for logger in self.logger.iter_mut() {
logger.flush();
}
}
}
pub struct ScopedLogger<L: Logger> {
/// Каквито полета решите, че ви трябват
tag: String,
base_logger: Box<L>
}
impl<L: Logger> ScopedLogger<L> {
pub fn new(tag: &str, base_logger: L) -> Self {
return ScopedLogger { tag: String::from(tag), base_logger: Box::new(base_logger) }
}
}
impl<L: Logger> Logger for ScopedLogger<L> {
/// Подходящи имплементации на Logger методите
fn push(&mut self, time: Instant, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().push(time, c.as_str());
}
fn log(&mut self, text: &str) {
let a : String = "[".to_owned();
let b : &str = "] ";
let c = a + self.tag.as_str() + b + text;
self.base_logger.as_mut().log(c.as_str())
}

Не би трябвало да е нужно да викаш as_mut() тук, понеже Box-а е "прозрачен" откъм методи -- извикването на метод вика deref или deref_mut когато е нужно и свежда box-а до reference.

Форматирането на низа можеше и да е по-просто като че ли :). И без малко особено-именувани променливи като a, b, и c. Аз съм го направил така:

self.base_logger.push(time, &format!("[{}] {}", self.tag, text));

А метода log няма нужда да повтаря кода. Можеше даже да е имплементиран в самия trait:

fn log(&mut self, text: &str) {
    self.push(Instant::now(), text)
}
fn try_flush(&mut self) -> io::Result<()> {
return Ok(());
}
fn flush(&mut self) {
self.base_logger.as_mut().flush();
}
}
fn main() {
let mut logger = BufferedLogger::new(io::stdout(), 100);
let now = Instant::now();
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(1), "Test1");
assert_eq!(logger.buffered_entries(), vec!["Test1", "Test2"]);
- let mut cloned_logger = logger.clone();
+ let cloned_logger = logger.clone();
assert_eq!(cloned_logger.buffered_entries(), vec!["Test1", "Test2"]);
logger.try_flush().unwrap();
logger.flush();
logger.log("Test");
assert_eq!(logger.buffered_entries().len(), 1);
let logger1 = BufferedLogger::new(Vec::new(), 100);
let logger2 = BufferedLogger::new(Vec::new(), 100);
let now = Instant::now();
let mut logger = MultiLogger::new();
logger.log_to(logger1.clone());
logger.push(now + Duration::from_millis(1), "Test1");
logger.log_to(ScopedLogger::new("Second", logger2.clone()));
logger.push(now + Duration::from_millis(2), "Test2");
logger.push(now + Duration::from_millis(3), "Test3");
assert_eq!(logger1.buffered_entries(), vec!["Test1", "Test2", "Test3"]);
assert_eq!(logger2.buffered_entries(), vec!["[Second] Test2", "[Second] Test3"]);
logger.try_flush().unwrap();
logger.flush();
let base = BufferedLogger::new(Vec::new(), 100);
let mut logger = ScopedLogger::new("Rust", ScopedLogger::new("FMI", base.clone()));
logger.log("Test");
assert_eq!(base.buffered_entries(), vec!["[FMI] [Rust] Test"]);
}

Казвали сме го и друг път, но: почвай по-отрано с домашното. Дай си време да напишеш малко по-интересни тестове, да експериментираш, да го огледаш за подобрения.