# 正課第五次作業 ###### tags: `NTOU CSE C++ Programming` > 教學文件和作業說明文件: https://hackmd.io/@kogiokka/ntou-cse-cpp-nav 本次實習主題是程式設計中的配接器模式(Adapter Pattern)。還有練習 C++ 的繼承(inheritance)、虛擬函式(virtual functions)和抽象類別(abstract class)。 ## 作業規定 * 繳交日期: 6月17日(以教師伺服器上顯示的日期為準) * 電子檔繳交方式:將程式碼和心得壓縮成 **ZIP** 檔上傳到教師的伺服器。檔案格式為`<學號>-<姓名>.zip`,例如:`11057666-陳小明.zip`。 * 紙本繳交方式:上課交紙本(A4紙)的程式碼和心得 ## 作業內容 承[第十二次實習作業](https://hackmd.io/@kogiokka/ntou-cse-cpp-practice-12)。依配接器模式(Adapter Pattern)設計一或多個 Adaptee 和 Adapter。負責測試案例的是範例程式中的 `Client` 類別,`Client` 會透過 `LookUpTable` 抽象類別呼叫 Adapters 所提供的函式。 ### 需求 * 每一個 Adaptee 類別都要搭配兩個 Adapter 類別,其中一個以必須以多重繼承(inheritance)的方法實作,而另一個以複合(composition)的方法實作。 * 設計出的 Adaptees 和 Adapters 須通過 `Client` 的測試案例。 ### 計分方式 * 有做出任一種 Adaptee:80 分 * 有 BinaryTree 類型的 Adaptee:20 分 * 每新增一種不同類型的 Adaptee:20 分 * 最高分 120 分 ## 範例程式 * [正課第五次範例程式:AdapterPattern_Enhanced(Google Drive)](https://drive.google.com/drive/folders/1Rrv5W7VA1odUakQM8FtKfNfLToo7qyhy?usp=sharing) * 專案的資料夾路徑不能有中文字元,否則用 Visual Studio 編譯 CMake 專案會出錯。 ### AdapterPattern_Enhanced 附帶的測試資料 `TestData/BookList.csv` 是逗號分隔值檔(comma-separated values file)。除了用 Spreadsheet 軟體(例:MS Excel)瀏覽和編輯,也可以用純文字檔的方式開啟和編輯(例:VS Code、Notepad++)。 #### 編譯和執行 > 不是使用 Visual Studio 嗎?請見 [CMake專案建置](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02)。 在 Visual Studio 選擇**開啟本機資料夾**或**檔案 → 開啟 → 資料夾**,並選擇`AdapterPattern_Enhanced/`的資料夾。Visual Studio 會自動開始設定 CMake 專案。等待輸出欄位的訊息跑完之後,在上方工具列的**選取啟動項目**選 `adapter-enhanced.exe (安裝)` 就可以自動編譯執行了。 如果不是使用 Visual Studio,可能需要自行定義 `CMAKE_INSTALL_PREFIX` 變數,並做其他設定。詳情請見[CMake專案建置](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02)的「CMake安裝指令的相關設定」一節。 #### 專案目錄結構 ```text AdapterPattern_Enhanced ├── CMakeLists.txt ├── Source │ ├── Adapter │ │ ├── AdapterClass.cpp │ │ ├── AdapterClass.hpp │ │ ├── AdapterObject.cpp │ │ └── AdapterObject.hpp │ ├── Book.cpp │ ├── Book.hpp │ ├── Client │ │ ├── Client.cpp │ │ └── Client.hpp │ ├── ClientInterface │ │ └── LookUpTable.hpp │ ├── CMakeLists.txt │ ├── main.cpp │ ├── Service │ │ ├── Adaptee.cpp │ │ └── Adaptee.hpp │ └── Utilities │ ├── Console.cpp │ ├── Console.hpp │ ├── Helpers.cpp │ ├── Helpers.hpp │ ├── Timer.cpp │ └── Timer.hpp └── TestData └── BookList.csv ``` #### 原始碼 `TestData/BookList.csv` ```csv= "C++17 STL Cookbook","Jacek Galowicz",49.99 "Boost C++ Application Development Cookbook Second Edition","Antony Polukhin",49.99 "Mastering C++ Programming","Jeganathan Swaminathan",49.99 "C++ High Performance","Viktor Sehr, Bjorn Andrist",44.99 "C++ Crash Course: A Fast-Paced Introduction Illustrated Edition","Josh Lospinoso",50.40 "C++ Primer","Stanley Lippman,Josée Lajoie,Barbara Moo",40.94 "Effective Modern C++","Scott Meyers",49.01 "C++ Programming Language (hardcover), The 4th Edition","Bjarne Stroustrup",66.99 "C++ Standard Library Quick Reference 1st ed. Edition","Peter Van Weert,Marc Gregoire",34.71 "The Modern C++ Challenge","Marius Bancila",34.99 "Hands-On Design Patterns with C++","Fedor G. Pikus",49.99 "Hands-On Embedded Programming with C++17","Maya Posch",44.99 "Hands-On System Programming with C++","Dr. Rian Quinn",44.99 "Modern C","Jens Gustedt",59.99 "C++ Concurrency in Action, Second Edition","Anthony Williams",69.99 "Modern Fortran","Milan Curcic",59.99 "Math for Programmers","Paul Orland",59.99 "The Java Module System","Nicolai Parlog,Kevlin Henney",49.99 "Modern Java in Action","Raoul-Gabriel Urma,Mario Fusco,Alan Mycroft",54.99 "Enterprise Java Microservices","Ken Finnigan",49.99 "Functional Programming in C++","Ivan Čukić",49.99 "Learning Perl Sixth Edition","Randal L. Schwartz,brian d foy,Tom Phoenix",24.06 "Programming Perl","Tom Christiansen,brian d foy,Larry Wall,Jon Orwant",27.00 "The Joy of Kotlin","Pierre-Yves Saumont",49.99 "Functional Programming in Java","Pierre-Yves Saumont",49.99 ``` `CMakeLists.txt` ```cmake= cmake_minimum_required(VERSION 3.12) project("Adapter Pattern") # 執行 "Source/" 資料夾中的 CMakeLists.txt add_subdirectory("Source/") ``` `Source/Book.cpp` ```cpp= #include "Book.hpp" using std::ostream; using std::string; using std::vector; Book::Book() : m_name("") , m_authors({}) , m_price(0) { } Book::Book(const string& name, const vector<string>& authors, float price) : m_name(name) , m_authors(authors) , m_price(price) { } void Book::assign(const string& name, const vector<string>& authors, float price) { m_name = name; m_authors = authors; m_price = price; } std::string Book::name() const { return m_name; } std::vector<std::string> Book::authors() const { return m_authors; } float Book::price() const { return m_price; } ``` `Source/Book.hpp` ```cpp= #ifndef BOOK_H_ #define BOOK_H_ #include <iostream> #include <string> #include <vector> class Book { friend std::ostream& operator<<(std::ostream&, const Book&); private: std::string m_name; std::vector<std::string> m_authors; float m_price; public: Book(); Book(const std::string& name, const std::vector<std::string>& authors, float price); void assign(const std::string& name, const std::vector<std::string>& authors, float price); std::string name() const; std::vector<std::string> authors() const; float price() const; }; #endif // BOOK_H_ ``` `Source/CMakeLists.txt` ```cmake= add_executable(adapter-enhanced) # 新增 include 路徑 # #include 指令中的相對路徑會從專案根目錄之下的 "Source" 資料夾開始計算 target_include_directories(adapter-enhanced PRIVATE "${CMAKE_SOURCE_DIR}/Source") target_sources(adapter-enhanced PRIVATE "Adapter/AdapterClass.cpp" "Adapter/AdapterObject.cpp" "Client/Client.cpp" "Book.cpp" "Service/Adaptee.cpp" "Utilities/Console.cpp" "Utilities/Helpers.cpp" "Utilities/Timer.cpp" "main.cpp" ) install(TARGETS adapter-enhanced DESTINATION .) install(DIRECTORY "${CMAKE_SOURCE_DIR}/TestData" DESTINATION .) ``` `Source/main.cpp` ```cpp= #include "Adapter/AdapterClass.hpp" #include "Adapter/AdapterObject.hpp" #include "Book.hpp" #include "Client/Client.hpp" #include "ClientInterface/LookUpTable.hpp" #include "Utilities/Console.hpp" #include "Utilities/Helpers.hpp" int main() { BANNER(); CUT(); Client client; client.importBooksFromFile("TestData/BookList.csv"); CUT(); /** // Adaptee 1 { HEAD1("AdapterClass1"); AdapterClass adapter1; client.test(&adapter1); END(); CUT(); HEAD1("AdapterObject1"); AdapterObject adapter2; client.test(&adapter2); END(); } */ /** // Adaptee 2 { HEAD1("AdapterClass2"); AdapterClass2 adapter1; client.test(&adapter1); END(); CUT(); HEAD1("AdapterObject2"); AdapterObject2 adapter2; client.test(&adapter2); END(); } */ PAUSE(); } ``` `Source/Adapter/AdapterClass.cpp` ```cpp= #include <limits> #include "Adapter/AdapterClass.hpp" #include "Utilities/Helpers.hpp" // TODO ``` `Source/Adapter/AdapterClass.hpp` ```cpp= #ifndef ADAPTERCLASS_H_ #define ADAPTERCLASS_H_ #include <string> #include <vector> #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" #include "Service/Adaptee.hpp" class AdapterClass : public LookUpTable, public Adaptee { public: int add(const Book& book) override; int remove(const std::string& bookName, const std::vector<std::string>& authors, float price) override; Book* find(const std::string& bookName) override; Book* find(const std::string& bookName, const std::vector<std::string>& authors) override; Book mostExpensive() override; Book cheapest() override; float average() override; void clean() override; }; #endif // ADAPTERCLASS_H_ ``` `Source/Adapter/AdapterObject.cpp` ```cpp= #include <limits> #include "Adapter/AdapterObject.hpp" #include "Utilities/Helpers.hpp" // TODO ``` `Source/Adapter/AdapterObject.hpp` ```cpp= #ifndef ADAPTEROBJECT_H_ #define ADAPTEROBJECT_H_ #include <string> #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" #include "Service/Adaptee.hpp" class AdapterObject : public LookUpTable { private: Adaptee ad; public: int add(const Book& val) override; int remove(const std::string& bookName, const std::vector<std::string>& authors, float price) override; Book* find(const std::string& bookName) override; Book* find(const std::string& bookName, const std::vector<std::string>& authors) override; Book mostExpensive() override; Book cheapest() override; float average() override; void clean() override; }; #endif // ADAPTEROBJECT_H_ ``` `Source/Client/Client.cpp` ```cpp= #include <cstdlib> #include <ctime> #include <fstream> #include <iomanip> #include <iostream> #include <sstream> #include <string> #include "Client.hpp" #include "Utilities/Console.hpp" #include "Utilities/Helpers.hpp" #include "Utilities/Timer.hpp" /* <string> */ using std::getline; using std::stof; // string to float using std::string; using std::to_string; /* <sstream> */ using std::istringstream; /* <fstream> */ using std::ifstream; /* <iostream> */ using std::cerr; using std::cout; using std::endl; /* <iomanip> */ using std::quoted; /* <vector> */ using std::vector; /* <cstdlib> */ using std::exit; using std::srand; /* <ctime> */ using std::time; /* Test Cases */ void Client::test(LookUpTable* table) { m_books.push_back(csv2Book(R"csv("Beginning C++20: From Novice to Professional","Ivor Horton",38.60)csv")); HEAD2("Test LookUpTable::add"); { string sep = ""; cout << "add: "; for (const auto& book : m_books) { cout << sep << table->add(book); // Ans: 1 sep = ", "; } cout << endl; } END(); HEAD2("Test Price Information"); { cout << "Most expensive book: " << table->mostExpensive() << endl; cout << "Cheapest book: " << table->cheapest() << endl; cout << "Average price: " << table->average() << endl; /** Ans: Most expensive book: {"C++ Concurrency in Action, Second Edition", ["Anthony Williams"], 69.99} Cheapest book: {"Learning Perl Sixth Edition", ["Randal L. Schwartz", "brian d foy", "Tom Phoenix"], 24.06} Average price: 48.3281 */ } END(); HEAD2("Test LookUpTable::add, again"); { string sep = ""; cout << "add: "; for (const auto& book : m_books) { cout << sep << table->add(book); // Ans: 0 sep = ", "; } cout << endl; } END(); HEAD2("Test Price Information, again"); { cout << "Most expensive book: " << table->mostExpensive() << endl; cout << "Cheapest book: " << table->cheapest() << endl; cout << "Average price: " << table->average() << endl; /** Ans: Most expensive book: {"C++ Concurrency in Action, Second Edition", ["Anthony Williams"], 69.99} Cheapest book: {"Learning Perl Sixth Edition", ["Randal L. Schwartz", "brian d foy", "Tom Phoenix"], 24.06} Average price: 48.3281 */ } END(); HEAD2("Test LookUpTable::remove"); { Book book("C++ High Performance", { "Viktor Sehr", "Bjorn Andrist" }, 44.99f); cout << "remove: " << table->remove(book.name(), book.authors(), book.price()) << "\n"; // Ans: remove: 1 } { Book book = csv2Book(R"csv("Modern Fortran","Mr. Curcic",0.0)csv"); cout << "remove: " << table->remove(book.name(), book.authors(), book.price()) << "\n"; // Ans: remove: 0 } { Book book = csv2Book(R"csv("Modern Fortran","Milan Curcic",59.99)csv"); cout << "remove: " << table->remove(book.name(), book.authors(), book.price()) << "\n"; // Ans: remove: 1 } { Book book = csv2Book( R"csv("C++ Concurrency in Action, Second Edition","Anthony Williams",69.99)csv"); // Ans: remove: 1 cout << "remove: " << table->remove(book.name(), book.authors(), book.price()) << "\n"; } string sep = ""; cout << "remove: "; for (const auto& book : m_books) { cout << sep << table->remove(book.name(), book.authors(), book.price()); sep = ", "; } cout << endl; END(); for (const auto& book : m_books) { table->add(book); } HEAD2("Test LookUpTable::find"); { int id = 18; Book* ptr = nullptr; string name = m_books[id].name(); vector<string> authors = m_books[id].authors(); ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; // Ans } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; } authors = { m_books[id].authors().front() }; ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; // Ans } id = 10; name = m_books[id].name(); authors = m_books[id].authors(); ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; // Ans } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; } authors = { m_books[id].authors().front() }; ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; // Ans } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; } } END(); HEAD2("Test LookUpTable::clean"); { Book* ptr = nullptr; string name = "Modern Java in Action"; vector<string> authors = { "Raoul-Gabriel Urma", "Mario Fusco", "Alan Mycroft" }; ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; // Ans } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; } table->clean(); cout << "Cleaned the LookUpTable." << endl; ptr = table->find(name, authors); if (ptr) { cout << "Success. Found " << *ptr << endl; } else { cout << "Fail. Could not find " << quoted(name) << ", " << authors << endl; // Ans } } END(); stressTesting(table); } void Client::stressTesting(LookUpTable* table) { HEAD2("Stress Testing"); { Timer timer; srand(time(0)); for (int i = 0; i < 1000; i++) { string str1 = "Stress Testing" + to_string(i); Book book(str1, { str1 }, static_cast<float>(rand() % 200) * 0.65f); table->add(book); } for (int i = 0; i < 1000; i++) { string str1 = "Stress Testing" + to_string(i); table->find(str1); } for (int i = 0; i < 1000; i++) { string str1 = "Stress Testing" + to_string(i); table->find(str1, { str1 }); } for (int i = 0; i < 1000; i++) { string str1 = "Stress Testing" + to_string(i); table->remove(str1, { str1 }, static_cast<float>(rand() % 200) * 0.65f); } } END(); } void Client::importBooksFromFile(const string& filename) { ifstream file; file.open(filename); if (!file.good()) { cerr << "Failed to open the file: " << quoted(filename) << endl; exit(EXIT_FAILURE); return; } Book book; string line; vector<Book> books; while (getline(file, line)) { books.push_back(csv2Book(line)); } file.close(); m_books.insert(m_books.end(), books.begin(), books.end()); cout << "Successfully import " << books.size() << " books:" << endl; for (unsigned int i = 1; i <= books.size(); i++) { cout << i << ". " << books[i - 1] << endl; } } void Client::importBookCSV(const string& csv) { m_books.push_back(csv2Book(csv)); } Book Client::csv2Book(const string& text) const { Book book; string name, authors, price; istringstream lineStream(text); lineStream >> quoted(name); // Get the quoted content lineStream.get(); // remove comma lineStream >> quoted(authors); // Get the quoted content lineStream.get(); // remove comma lineStream >> price; lineStream.get(); // remove comma // split function is from Utilities/Helper book.assign(name, split(authors, ','), stof(price)); return book; } void Client::clearBooks() { m_books.clear(); } ``` `Source/Client/Client.hpp` ```cpp= #ifndef CLIENT_H_ #define CLIENT_H_ #include <string> #include <vector> #include "Book.hpp" #include "ClientInterface/LookUpTable.hpp" class Client { std::vector<Book> m_books; public: void test(LookUpTable* table); void stressTesting(LookUpTable* table); void importBooksFromFile(const std::string& filename); void importBookCSV(const std::string& csv); void clearBooks(); private: Book csv2Book(const std::string& text) const; }; #endif // CLIENT_H_ ``` `Source/ClientInterface/LookUpTable.hpp` ```cpp= #ifndef LOOKUPTABLE_H_ #define LOOKUPTABLE_H_ #include <string> #include <vector> #include "Book.hpp" class LookUpTable { public: /** Add a book into the table @param book the book to be added @return 1 for success and 0 for failure */ virtual int add(const Book& book) = 0; /** Remove a book from the table @param name name of the book @param authors authors of the book @param price price of the book @return 1 for success and 0 for failure */ virtual int remove(const std::string& name, const std::vector<std::string>& authors, float price) = 0; /** Find a book from the table @param name name of the book @return pointer to the book if the search succeed, otherwise nullptr */ virtual Book* find(const std::string& name) = 0; /** Find a book from the table @param name name of the book @param authors authors of the book @return pointer to the book if the search succeed, otherwise nullptr */ virtual Book* find(const std::string& name, const std::vector<std::string>& authors) = 0; /** Get the most expensive book from the table @return the most expensive book */ virtual Book mostExpensive() = 0; /** Get the cheapest book from the table @return the cheapest book */ virtual Book cheapest() = 0; /** Get the average price of the books in the table @return the average price */ virtual float average() = 0; /** Remove all the books from the table */ virtual void clean() = 0; }; #endif // LOOKUPTABLE_H_ ``` `Source/Service/Adaptee.cpp` ```cpp= #include "Service/Adaptee.hpp" #include "Utilities/Helpers.hpp" /** ~~~~ TODO ~~~~ 需求 ---- 每一個 Adaptee 類別都要搭配兩個 Adapter 類別。其中一個以多重繼承(inheritance )的方法實作,而另一個以複合(composition)的方法實作。 計分 ---- * 任一種 Adaptee:80 分 * 有 BinaryTree 類型的 Adaptee:20 分 * 每新增一種不同類型的 Adaptee:20 分 * 最高分 120 分 */ ``` `Source/Service/Adaptee.hpp` ```cpp= #ifndef ADAPTEE_H_ #define ADAPTEE_H_ #include <map> #include <string> #include "Book.hpp" class Adaptee { std::map<std::string, Book> mapBook; public: // TODO }; // class BinaryTree // { // }; #endif // ADAPTEE_H_ ``` `Source/Utilities/Console.cpp` ```cpp= #include <iostream> #include "Utilities/Console.hpp" /* <iostream> */ using std::cout; using std::endl; /* <string> */ using std::string; static void _HEAD_INTERNAL(const string& text, char ch); void BANNER() { cout << R"AdapterPattern2( ** ** ** **** /** ****** /** **//** /** ****** /**///** ****** ***** ****** ** //** ****** //////** /** /**///**/ **///**//**//* ********** **///** ******* /****** /** /******* /** / /**//////**/** /** **////** /**/// /** /**//// /** /** /**//******//********/** //** //******/*** // // ////// //////// // // ////// /// ******* ** ** /**////** /** /** /** /** ****** ****** ****** ***** ****** ******* /******* //////** ///**/ ///**/ **///**//**//*//**///** /**//// ******* /** /** /******* /** / /** /** /** **////** /** /** /**//// /** /** /** /** //******** //** //** //******/*** *** /** // //////// // // ////// /// /// // ******** ** ** /**///// /** /** /** ******* /** ****** ******* ***** ***** /** /******* //**///**/****** //////** //**///** **///** **///** ****** /**//// /** /**/**///** ******* /** /**/** // /******* **///** /** /** /**/** /** **////** /** /**/** **/**//// /** /** /******** *** /**/** /**//******** *** /**//***** //******//****** //////// /// // // // //////// /// // ///// ////// ////// )AdapterPattern2"; } void HEAD1(const string& text) { string tildes(text.length(), '~'); cout << tildes << "\n"; cout << text << "\n"; cout << tildes << "\n\n"; } void HEAD2(const string& text) { _HEAD_INTERNAL(text, '^'); } void HEAD3(const string& text) { _HEAD_INTERNAL(text, '*'); } void END() { cout << endl; } void CUT() { string line(80, '='); cout << line << "\n" << endl; } // Portable system("pause") void PAUSE() { cout << "Press enter to continue..." << endl; std::cin.get(); } static inline void _HEAD_INTERNAL(const string& text, char ch) { string line(text.length(), ch); cout << text << "\n"; cout << line << "\n\n"; } ``` `Source/Utilities/Console.hpp` ```cpp= #ifndef CONSOLE_H_ #define CONSOLE_H_ #include <string> void BANNER(); void HEAD1(const std::string& text); void HEAD2(const std::string& text); void HEAD3(const std::string& text); void END(); void CUT(); void PAUSE(); #endif // CONSOLE_H_ ``` `Source/Utilities/Helpers.cpp` ```cpp= #include <iomanip> #include <sstream> #include <string> #include "Helpers.hpp" /* <iostream> */ using std::cout; using std::endl; using std::ostream; /* <string> */ using std::getline; using std::string; /* <sstream> */ using std::istringstream; using std::ostringstream; /* <map> */ using std::map; /* <vector> */ using std::vector; /* <iomanip> */ using std::quoted; vector<string> split(string text, char delim) { vector<string> result; string token; istringstream textStream(text); while (getline(textStream, token, delim)) { result.push_back(token); } return result; } ostream& operator<<(ostream& out, const vector<string>& vec) { ostringstream vectorStream; string separator = ""; vectorStream << "["; for (auto it = vec.begin(); it != vec.end(); ++it) { vectorStream << separator << quoted(*it); separator = ", "; } vectorStream << "]"; return (out << vectorStream.str()); } ostream& operator<<(ostream& out, const Book& book) { ostringstream bookStream; bookStream << "{" << quoted(book.m_name) << ", " << book.m_authors << ", " << book.m_price << "}"; return (out << bookStream.str()); } ostream& operator<<(ostream& out, const map<string, Book>& books) { ostringstream booksJSON; string separator = ""; const string indent = " "; booksJSON << "{\n"; for (auto it = books.begin(); it != books.end(); ++it) { booksJSON << separator << indent << quoted(it->first) << ": {\n" << indent << indent << quoted("name") << ": " << quoted(it->second.name()) << ",\n" << indent << indent << quoted("authors") << ": " << it->second.authors() << ",\n" << indent << indent << quoted("price") << ": " << it->second.price() << "\n" << indent << "}"; separator = ",\n"; } booksJSON << "\n}" << endl; return (out << booksJSON.str()); } bool operator==(const std::vector<std::string>& vec1, const std::vector<std::string>& vec2) { if (vec1.size() != vec2.size()) { return false; } for (unsigned int i = 0; i < vec1.size(); i++) { if (vec1[i] != vec2[i]) { return false; } } return true; } ``` `Source/Utilities/Helpers.hpp` ```cpp= #ifndef HELPERS_H_ #define HELPERS_H_ #include <iostream> #include <map> #include <string> #include <vector> #include "Book.hpp" std::vector<std::string> split(std::string text, char delim); std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& vec); std::ostream& operator<<(std::ostream& out, const Book& book); std::ostream& operator<<(std::ostream& out, const std::map<std::string, Book>& books); bool operator==(const std::vector<std::string>& vec1, const std::vector<std::string>& vec2); #endif // HELPERS_H_ ``` `Source/Utilities/Timer.cpp` ```cpp= #include "Timer.hpp" #include <iostream> #include <ratio> /* <chrono> */ namespace chr = std::chrono; /* <ratio> */ using std::ratio; /* <iostream> */ using std::cout; using std::endl; Timer::Timer() { m_start = chr::steady_clock::now(); } Timer::~Timer() { const auto end = chr::steady_clock::now(); chr::duration<double, ratio<1, 1>> secs = end - m_start; chr::duration<double, ratio<1, 1000000>> microSecs = end - m_start; cout << "The operation(s) took " << secs.count() << " seconds (" << microSecs.count() << " microseconds)." << endl; } ``` `Source/Utilities/Timer.hpp` ```cpp= #ifndef TIMER_H_ #define TIMER_H_ #include <chrono> /** * @class Timer * * The timer will start right after the instantiation, and print the duration to * standard output when the object is destroyed. */ class Timer { std::chrono::time_point<std::chrono::steady_clock> m_start; public: Timer(); ~Timer(); }; #endif // TIMER_H_ ```