#include <algorithm>
#include <iostream>
#include <iterator>
#include <memory>
#include <ranges>
#include <sstream>
#include <vector>

namespace rn = std::ranges;

struct Student {
    int index_;
    std::string name_;
    double average_;
};

std::vector<Student*> filterStudents(const std::vector<std::unique_ptr<Student>>& students, double minAverage) {
    std::vector<Student*> result;

    rn::copy(students |
                 std::views::transform([](const auto& student) { return student.get(); }) |
                 std::views::filter([minAverage](const auto& student) { return student->average_ <= minAverage; }),
             std::back_inserter(result));

    return result;
}

int main() {
    std::vector<std::unique_ptr<Student>> students;
    students.push_back(std::make_unique<Student>(123456, "Jordan", 4.53));
    students.push_back(std::make_unique<Student>(654321, "Michael", 4.51));
    students.push_back(std::make_unique<Student>(246892, "John", 4.56));
    students.push_back(std::make_unique<Student>(743561, "Jane", 4.44));
    students.push_back(std::make_unique<Student>(811111, "Anna", 4.46));
    students.push_back(std::make_unique<Student>(811111, "Tom", 4.36));
    students.push_back(std::make_unique<Student>(811111, "Jerry", 4.56));
    students.push_back(std::make_unique<Student>(811111, "Mike", 4.45));

    const auto res = filterStudents(students, 4.45);
    rn::transform(res,
                  std::ostream_iterator<std::string>(std::cout, "\n"),
                  [](const Student* student) {
                      std::ostringstream os;
                      os << "Name: " << student->name_
                         << " | index: " << student->index_
                         << " | average: " << student->average_;
                      return os.str();
                  });
}