упр.04 задача 2

Предадени решения

Краен срок:
05.11.2025 23:59
Точки:
2

Срокът за предаване на решения е отминал

// Include the solution source in the same file, so we
// don't have to worry about item visibility.
// Please don't use `include!` in real code, this is a hack
// around the checking system.
include!{ "../src/lib.rs" }
fn get_books() -> Vec<Book> {
vec![
Book {
title: "The Rust Book".into(),
author: "Steve".into(),
year: 2018,
rating: 4.7,
},
Book {
title: "Rust Patterns".into(),
author: "Anna".into(),
year: 2020,
rating: 4.9,
},
Book {
title: "C Programming".into(),
author: "Dennis".into(),
year: 1978,
rating: 4.3,
},
]
}
fn approx_eq(a: f32, b: f32) -> bool {
let eps = 100.0 * f32::EPSILON;
if a.is_nan() || b.is_nan() || a.signum() != b.signum() {
return false;
}
((a - b).abs() / f32::max(a, b)) < eps
}
macro_rules! assert_approx_eq {
($a: expr, $b: expr) => {
if !approx_eq($a, $b) {
panic!("assertion approx_eq failed: left={:?}, right={:?}", $a, $b);
}
};
}
#[test]
fn test_compare() {
let books = get_books();
assert_eq!(books[0].compare(&books[0]), CompareResult::Equal);
assert_eq!(books[0].compare(&books[1]), CompareResult::Less);
assert_eq!(books[1].compare(&books[0]), CompareResult::Greater);
}
#[test]
fn test_matches() {
let books = get_books();
assert_eq!(books[0].matches("Rust"), true);
assert_eq!(books[2].matches("Rust"), false);
}
#[test]
fn test_aggregate() {
let books = get_books();
assert!(Book::aggregate(&[]).is_nan() || Book::aggregate(&[]) == 0.0);
assert_eq!(Book::aggregate(&books[0..1]), 4.7);
assert_approx_eq!(Book::aggregate(&books[1..]), 4.6);
assert_approx_eq!(Book::aggregate(&books[..]), 4.633333);
}
#[test]
fn test_process() {
let (result, avg_rating) = process_items(get_books(), "Rust", SortOrder::Desc);
assert_eq!(
result.iter().map(|book| book.title.as_str()).collect::<Vec<_>>(),
vec!["Rust Patterns", "The Rust Book"]
);
assert_approx_eq!(avg_rating, 4.8);
let (result, avg_rating) = process_items(get_books(), "Rust", SortOrder::Desc);
assert_eq!(
result.iter().map(|book| book.title.as_str()).collect::<Vec<_>>(),
vec!["Rust Patterns", "The Rust Book"]
);
assert_approx_eq!(avg_rating, 4.8);
}

Цел: Да се реализира библиотека, която работи с произволни типове данни, като им предоставя три функционалности:

  1. Филтриране по текстова заявка.
  2. Сортиране, базирано на собствена система от сравнения (без Ordering).
  3. Агрегиране, което изчислява "стойност" за целия списък според типа на елементите.

Описание

1. Създай собствен тип за сравнение:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CompareResult {
    Less,
    Equal,
    Greater,
}

2. Създай trait Compare:

trait Compare {
    fn compare(&self, other: &Self) -> CompareResult;
}

3. Създай trait Filter:

trait Filter {
    fn matches(&self, query: &str) -> bool;
}

4. Създай trait Aggregate:

trait Aggregate {
    type Output;
    fn aggregate(items: &[Self]) -> Self::Output
    where
        Self: Sized;
}

// Забележка: не сме говорили за асоциирани типове.
// Идеята в случая е, че Аggregate за книги ще върне f32, докато Aggregate за друг тип може да върне нещо друго.
// Очаква се да напишете
// 
//    impl Aggregate for Book {
//        type Output = f32;
//        ... 

5. Имплементирай функция:

fn process_items<T>(items: Vec<T>, query: &str, order: SortOrder) -> (Vec<T>, T::Output)
where
    T: Compare + Filter + Aggregate + Clone,
{
    // TODO: имплементация
}

като имате

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SortOrder {
  Desc,
  Asc,
}

Примерен тип

#[derive(Clone)]
struct Book {
    title: String,
    author: String,
    year: u32,
    rating: f32,
}

Имплементации:

  • matches връща true, ако query се съдържа в title или author.
  • compare сравнява по year, като по-новите са “по-големи”.
  • aggregate изчислява среден rating за всички книги.

Допълнителни усложнения

  1. Aggregate трябва да има асоцииран тип (type Output).
  2. process_items трябва да използва правилно асоциирания тип.
  3. Не може да се използват std::cmp::Ordering, sort_by, Ord, PartialOrd.
  4. Да се внимава с borrow checker-а при сортиране.
  5. Функцията връща два различни типа (вектор + агрегат).
  6. Примерен резултат
fn main() {
    let books = vec![
        Book { title: "The Rust Book".into(), author: "Steve".into(), year: 2018, rating: 4.7 },
        Book { title: "Rust Patterns".into(), author: "Anna".into(), year: 2020, rating: 4.9 },
        Book { title: "C Programming".into(), author: "Dennis".into(), year: 1978, rating: 4.3 },
    ];

    let (result, avg_rating) = process_items(books, "Rust", SortOrder::Asc);
    println!("Filtered & sorted:");
    for b in &result {
        println!("{} by {} ({})", b.title, b.author, b.year);
    }
    println!("Average rating: {:.2}", avg_rating);
}

Очакван изход:

Filtered & sorted:
The Rust Book by Steve (2018)
Rust Patterns by Anna (2020)
Average rating: 4.80

Задължително прочетете (или си припомнете): Указания за предаване на домашни

Погрижете се решението ви да се компилира с базовия тест:

// Include the solution source in the same file, so we
// don't have to worry about item visibility.
// Please don't use `include!` in real code, this is a hack
// around the checking system.
include!{ "../src/lib.rs" }
fn get_books() -> Vec<Book> {
vec![
Book {
title: "The Rust Book".into(),
author: "Steve".into(),
year: 2018,
rating: 4.7,
},
Book {
title: "Rust Patterns".into(),
author: "Anna".into(),
year: 2020,
rating: 4.9,
},
Book {
title: "C Programming".into(),
author: "Dennis".into(),
year: 1978,
rating: 4.3,
},
]
}
fn approx_eq(a: f32, b: f32) -> bool {
let eps = 100.0 * f32::EPSILON;
if a.is_nan() || b.is_nan() || a.signum() != b.signum() {
return false;
}
((a - b).abs() / f32::max(a, b)) < eps
}
macro_rules! assert_approx_eq {
($a: expr, $b: expr) => {
if !approx_eq($a, $b) {
panic!("assertion approx_eq failed: left={:?}, right={:?}", $a, $b);
}
};
}
#[test]
fn test_compare() {
let books = get_books();
assert_eq!(books[0].compare(&books[0]), CompareResult::Equal);
assert_eq!(books[0].compare(&books[1]), CompareResult::Less);
assert_eq!(books[1].compare(&books[0]), CompareResult::Greater);
}
#[test]
fn test_matches() {
let books = get_books();
assert_eq!(books[0].matches("Rust"), true);
assert_eq!(books[2].matches("Rust"), false);
}
#[test]
fn test_aggregate() {
let books = get_books();
assert!(Book::aggregate(&[]).is_nan() || Book::aggregate(&[]) == 0.0);
assert_eq!(Book::aggregate(&books[0..1]), 4.7);
assert_approx_eq!(Book::aggregate(&books[1..]), 4.6);
assert_approx_eq!(Book::aggregate(&books[..]), 4.633333);
}
#[test]
fn test_process() {
let (result, avg_rating) = process_items(get_books(), "Rust", SortOrder::Desc);
assert_eq!(
result.iter().map(|book| book.title.as_str()).collect::<Vec<_>>(),
vec!["Rust Patterns", "The Rust Book"]
);
assert_approx_eq!(avg_rating, 4.8);
let (result, avg_rating) = process_items(get_books(), "Rust", SortOrder::Desc);
assert_eq!(
result.iter().map(|book| book.title.as_str()).collect::<Vec<_>>(),
vec!["Rust Patterns", "The Rust Book"]
);
assert_approx_eq!(avg_rating, 4.8);
}