diff --git a/lab4/.vscode/settings.json b/lab4/.vscode/settings.json new file mode 100644 index 0000000..b73e5a3 --- /dev/null +++ b/lab4/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "regex": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "typeinfo": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/lab4/Sieve.cc b/lab4/Sieve.cc new file mode 100644 index 0000000..9360a66 --- /dev/null +++ b/lab4/Sieve.cc @@ -0,0 +1,74 @@ +#include +#include +#include + +class Sieve { +private: + std::string sieve; + +public: + // Constructor to initialize sieve with 'P' (prime assumption) + Sieve(size_t limit) : sieve(limit + 1, 'P') { + // 0 and 1 are not primes + sieve[0] = 'C'; + if (limit > 0) sieve[1] = 'C'; + + // Sieve of Eratosthenes + for (size_t i = 2; i * i <= limit; ++i) { + if (sieve[i] == 'P') { + for (size_t j = i * i; j <= limit; j += i) { + sieve[j] = 'C'; + } + } + } + } + + // Get primes as a vector of integers + std::vector getPrimes() const { + std::vector primes; + for (size_t i = 2; i < sieve.size(); ++i) { + if (sieve[i] == 'P') { + primes.push_back(i); + } + } + return primes; + } + + // Print all primes in the range + void printPrimesInRange(int start, int end) const { + for (int i = start; i <= end; ++i) { + if (sieve[i] == 'P') { + std::cout << i << " "; + } + } + std::cout << std::endl; + } + + // Get the largest prime below a limit + int largestPrimeBelow(int limit) const { + for (int i = limit; i >= 2; --i) { + if (sieve[i] == 'P') { + return i; + } + } + return -1; // If no prime is found + } +}; + +// Main function for testing +int main() { + const int limit = 100000; + + // Create a Sieve instance for the range 0 to limit + Sieve sieve(limit); + + // Print primes between 1 and 200 + std::cout << "Primes between 1 and 200:" << std::endl; + sieve.printPrimesInRange(1, 200); + + // Find and print the largest prime below 100,000 + int largestPrime = sieve.largestPrimeBelow(limit); + std::cout << "Largest prime below " << limit << ": " << largestPrime << std::endl; + + return 0; +} diff --git a/lab4/TagRemover.cc b/lab4/TagRemover.cc new file mode 100644 index 0000000..3f28d28 --- /dev/null +++ b/lab4/TagRemover.cc @@ -0,0 +1,32 @@ +#include +#include +#include + +class TagRemover { +private: + std::string content; + +public: + TagRemover(std::istream& input) { + std::string line; + while (std::getline(input, line)) { + content += line + '\n'; + } + } + + void print(std::ostream& output) const { + std::string result = std::regex_replace(content, std::regex("<[^>]*>"), ""); + result = std::regex_replace(result, std::regex("<"), "<"); + result = std::regex_replace(result, std::regex(">"), ">"); + result = std::regex_replace(result, std::regex(" "), " "); + result = std::regex_replace(result, std::regex("&"), "&"); + + output << result; + } +}; + +int main() { + TagRemover tr(std::cin); + tr.print(std::cout); + return 0; +} \ No newline at end of file diff --git a/lab4/date.cc b/lab4/date.cc index ee9b34a..bf6e37d 100644 --- a/lab4/date.cc +++ b/lab4/date.cc @@ -1,30 +1,86 @@ -#include // time and localtime +#include // för tid och localtime +#include // för setw och setfill +#include // för inputhantering #include "date.h" int Date::daysPerMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +// Konstruktor: dagens datum Date::Date() { - time_t timer = time(0); // time in seconds since 1970-01-01 - tm* locTime = localtime(&timer); // broken-down time - year = 1900 + locTime->tm_year; - month = 1 + locTime->tm_mon; - day = locTime->tm_mday; + time_t timer = time(0); // tid i sekunder sedan 1970-01-01 + tm* locTime = localtime(&timer); // lokal tid + year = 1900 + locTime->tm_year; + month = 1 + locTime->tm_mon; + day = locTime->tm_mday; } -Date::Date(int y, int m, int d) {} +// Konstruktor: specifikt datum +Date::Date(int y, int m, int d) : year(y), month(m), day(d) {} +// Get-funktioner int Date::getYear() const { - return 0; + return year; } int Date::getMonth() const { - return 0; + return month; } int Date::getDay() const { - return 0; + return day; } +// Gå till nästa dag void Date::next() { + day++; + if (day > daysPerMonth[month - 1] + (month == 2 && isLeapYear(year))) { + day = 1; + month++; + if (month > 12) { + month = 1; + year++; + } + } } +// Kontrollera om ett år är ett skottår +bool Date::isLeapYear(int year) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); +} + +// Overloaded operator<< (output) +std::ostream& operator<<(std::ostream& os, const Date& date) { + os << std::setw(4) << std::setfill('0') << date.getYear() << '-' + << std::setw(2) << std::setfill('0') << date.getMonth() << '-' + << std::setw(2) << std::setfill('0') << date.getDay(); + return os; +} + +// Overloaded operator>> (input) +std::istream& operator>>(std::istream& is, Date& date) { + std::string input; + is >> input; + + std::istringstream iss(input); + char dash1, dash2; + int y, m, d; + + if (iss >> y >> dash1 >> m >> dash2 >> d && dash1 == '-' && dash2 == '-') { + // Validera månad och dag + if (m >= 1 && m <= 12) { + int maxDay = Date::daysPerMonth[m - 1]; + if (m == 2 && Date::isLeapYear(y)) { + maxDay = 29; // Februari har 29 dagar under skottår + } + + if (d >= 1 && d <= maxDay) { + date = Date(y, m, d); // Sätt datumet om det är giltigt + return is; + } + } + } + + // Ogiltig inmatning + is.setstate(std::ios_base::failbit); + return is; +} diff --git a/lab4/date.h b/lab4/date.h index 9cfecaa..25b57df 100644 --- a/lab4/date.h +++ b/lab4/date.h @@ -1,19 +1,27 @@ #ifndef DATE_H #define DATE_H +#include + class Date { public: - Date(); // today's date - Date(int y, int m, int d); // yyyy-mm-dd - int getYear() const; // get the year - int getMonth() const; // get the month - int getDay() const; // get the day - void next(); // advance to next day + Date(); // dagens datum + Date(int y, int m, int d); // yyyy-mm-dd + int getYear() const; // returnerar året + int getMonth() const; // returnerar månaden + int getDay() const; // returnerar dagen + void next(); // går till nästa dag + + // Overloaded operators + friend std::ostream& operator<<(std::ostream& os, const Date& date); + friend std::istream& operator>>(std::istream& is, Date& date); + private: - int year; // the year (four digits) - int month; // the month (1-12) - int day; // the day (1-..) - static int daysPerMonth[12]; // number of days in each month + int year; // året (fyra siffror) + int month; // månaden (1-12) + int day; // dagen (1-...) + static int daysPerMonth[12]; // antal dagar i varje månad + static bool isLeapYear(int year); // kontrollera skottår }; #endif diff --git a/lab4/date_test.cc b/lab4/date_test.cc index abd24c9..c66ee07 100644 --- a/lab4/date_test.cc +++ b/lab4/date_test.cc @@ -1,64 +1,40 @@ #include -#include // for setw and setfill #include "date.h" -using std::cout; -using std::endl; -using std::setw; -using std::setfill; - -/* - * Prints the date d in the format yyyy-mm-dd. You shall replace this - * function with an overloaded operator<<, and add an overloaded operator>>. - * - */ -void print(const Date& d) { - cout << setw(4) << setfill('0') << d.getYear() << '-'; - cout << setw(2) << setfill('0') << d.getMonth() << '-'; - cout << setw(2) << setfill('0') << d.getDay(); -} - int main() { - // Check input and output of dates. Uncomment the following when you - // have added operator>> and operator<<. - /* - bool cont = true; - while (cont) { - cout << "Type a date: "; - Date aDate; - cin >> aDate; - if (cin.eof()) { - cont = false; - } else if (!cin.good()) { - cout << "Wrong input format" << endl; - // restore stream state and ignore the rest of the line - cin.clear(); - cin.ignore(10000, '\n'); - } - else { - cout << "Output: " << aDate << endl; - } - } - */ - - // Check 'next' by creating an object describing today's date, then - // printing dates more than a month ahead - cout << "--- Today and more than a month ahead:" << endl; - Date d1; - print(d1); - cout << endl; - for (int i = 1; i <= 35 ; ++i) { - d1.next(); - print(d1); - cout << endl; - } - - // Check so 'next' functions correctly from one year to the next - cout << "--- New Year's Eve and the next day:" << endl; - Date d2(2013, 12, 31); - print(d2); - cout << endl; - d2.next(); - print(d2); - cout << endl; + // Test input och output + bool cont = true; + while (cont) { + std::cout << "Type a date: "; + Date aDate; + std::cin >> aDate; + + if (std::cin.eof()) { + cont = false; + } else if (!std::cin.good()) { + std::cout << "Wrong input format" << std::endl; + std::cin.clear(); + std::cin.ignore(10000, '\n'); + } else { + std::cout << "Output: " << aDate << std::endl; + } + } + + // Testa 'next' med dagens datum + std::cout << "--- Today and more than a month ahead:" << std::endl; + Date d1; + std::cout << d1 << std::endl; + for (int i = 1; i <= 35; ++i) { + d1.next(); + std::cout << d1 << std::endl; + } + + // Testa 'next' från nyårsafton + std::cout << "--- New Year's Eve and the next day:" << std::endl; + Date d2(2013, 12, 31); + std::cout << d2 << std::endl; + d2.next(); + std::cout << d2 << std::endl; + + return 0; } diff --git a/lab4/makefile b/lab4/makefile new file mode 100644 index 0000000..ca93933 --- /dev/null +++ b/lab4/makefile @@ -0,0 +1,56 @@ +# Compiler +CXX = g++ +CXXFLAGS = -std=c++17 -Wall -Wextra -pedantic + +# Targets +TARGETS = TagRemover Sieve date_test toString_test string_cast_test + +# Source files +SRCS_TAGREMOVER = TagRemover.cc +SRCS_SIEVE = Sieve.cc +SRCS_DATE = date.cc +SRCS_DATE_TEST = date_test.cc +SRCS_TOSTRING_TEST = toString_test.cc date.cc +SRCS_STRING_CAST_TEST = string_cast_test.cc date.cc + +# Object files +OBJS_TAGREMOVER = $(SRCS_TAGREMOVER:.cc=.o) +OBJS_SIEVE = $(SRCS_SIEVE:.cc=.o) +OBJS_DATE = $(SRCS_DATE:.cc=.o) +OBJS_DATE_TEST = $(SRCS_DATE_TEST:.cc=.o) +OBJS_TOSTRING_TEST = $(SRCS_TOSTRING_TEST:.cc=.o) +OBJS_STRING_CAST_TEST = $(SRCS_STRING_CAST_TEST:.cc=.o) + +# Default target +all: $(TARGETS) + +# Rule to build TagRemover +TagRemover: $(OBJS_TAGREMOVER) + $(CXX) $(CXXFLAGS) -o $@ $^ + +# Rule to build Sieve +Sieve: $(OBJS_SIEVE) + $(CXX) $(CXXFLAGS) -o $@ $^ + +# Rule to build date_test +date_test: $(OBJS_DATE_TEST) $(OBJS_DATE) + $(CXX) $(CXXFLAGS) -o $@ $^ + +# Rule to build toString_test +toString_test: $(OBJS_TOSTRING_TEST) + $(CXX) $(CXXFLAGS) -o $@ $^ + +# Rule to build string_cast_test +string_cast_test: $(OBJS_STRING_CAST_TEST) + $(CXX) $(CXXFLAGS) -o $@ $^ + +# Rule to compile source files into object files +%.o: %.cc + $(CXX) $(CXXFLAGS) -c $< + +# Clean up build artifacts +clean: + rm -f $(OBJS_TAGREMOVER) $(OBJS_SIEVE) $(OBJS_DATE) $(OBJS_DATE_TEST) $(OBJS_TOSTRING_TEST) $(OBJS_STRING_CAST_TEST) $(TARGETS) + +# Phony targets +.PHONY: all clean diff --git a/lab4/reflektion.txt b/lab4/reflektion.txt new file mode 100644 index 0000000..048257c --- /dev/null +++ b/lab4/reflektion.txt @@ -0,0 +1,61 @@ +1. In your tests, how did you test the error handling (e.g., that a wrong string_cast actually +throws?) + +Genom att använda en ogiltig inmatning, som "abc" eller "123abc", och verifiera att std::invalid_argument kastas korrekt. + + +2. In TagRemover, why do you think the constructor takes an istream instead of just the +filename? + +Det gör klassen mer flexibel eftersom den kan arbeta med vilken ström som helst, inte bara filer. Till exempel kan den användas med std::cin eller std::stringstream. + + +3. In TagRemover, did you process the file line by line, or did you first read the entire file? +What are the pros and cons of these two approaches? + +Jag läste hela filen först eftersom det är enklare att använda regex för att bearbeta hela texten på en gång. +Fördelen är att det är mer effektivt för regex-matchning, men nackdelen är att det kräver mer minne för stora filer. + + +4. How do you read the entire contents of an std::istream into a std::string without using +a for or while loop? + +Genom att använda: +std::string content((std::istreambuf_iterator(istream)), std::istreambuf_iterator()); + + +5. In TagRemover, do you have duplicate code for translating the special characters? If so, how +would you refactor your code to avoid duplicate code? + +Ja, det finns duplicerad kod för varje specialtecken. Jag skulle använda en std::map för att lagra mönster och ersättningar och iterera genom den: +std::map replacements = { + {"<", "<"}, {">", ">"}, {" ", " "}, {"&", "&"} +}; +for (const auto& [pattern, replacement] : replacements) { + result = std::regex_replace(result, std::regex(pattern), replacement); +} + + +6. How do you check if an input or output operation on a stream (e.g., operator>> or +operator<<) has failed? + +Genom att använda std::istream::fail() eller std::ostream::fail(). + + +7. How do you know if you have reached the end of an istream? + +Genom att använda std::istream::eof(). + + +8. Does string_cast("123kalle") return the value 123 or does it throw an exception? +How do you implement each of those behaviours? + +Det kastar ett undantag eftersom std::istringstream misslyckas med att konsumera hela strängen. För att tillåta delvis inläsning skulle vi behöva anpassa funktionen. + + +9. When calling the function template toString, the template type argument is not ex- +plicitly given in the call. For string_cast, on the other hand, you have to specify +string_cast or string_cast. What is the difference? When should explicit +template arguments be given to function templates? + +toString använder <<-operatören, som automatiskt identifierar typen av objekt, medan string_cast kräver en explicit typ eftersom det behöver veta vad strängen ska konverteras till. diff --git a/lab4/string_cast.h b/lab4/string_cast.h new file mode 100644 index 0000000..cc258be --- /dev/null +++ b/lab4/string_cast.h @@ -0,0 +1,14 @@ +#include +#include +#include // För std::invalid_argument + +// Template function for string_cast +template +T string_cast(const std::string& str) { + std::istringstream iss(str); + T value; + if (!(iss >> value) || !(iss.eof())) { + throw std::invalid_argument("Invalid conversion from string: " + str); + } + return value; +} diff --git a/lab4/string_cast_test.cc b/lab4/string_cast_test.cc new file mode 100644 index 0000000..23a812a --- /dev/null +++ b/lab4/string_cast_test.cc @@ -0,0 +1,29 @@ +#include +#include +#include "date.h" // För Date-klassen +#include "string_cast.h" // Inkludera string_cast + +int main() { + try { + // Testa string_cast med int + int i = string_cast("123"); + std::cout << "Integer: " << i << std::endl; + + // Testa string_cast med double + double d = string_cast("12.34"); + std::cout << "Double: " << d << std::endl; + + // Testa string_cast med Date + Date date = string_cast("2015-01-10"); + std::cout << "Date: " << date << std::endl; + + // Testa ogiltig konvertering + int invalid = string_cast("abc"); + std::cout << "Invalid conversion: " << invalid << std::endl; + + } catch (const std::invalid_argument& e) { + std::cout << "Error: " << e.what() << std::endl; + } + + return 0; +} diff --git a/lab4/toString.h b/lab4/toString.h new file mode 100644 index 0000000..c4f0154 --- /dev/null +++ b/lab4/toString.h @@ -0,0 +1,10 @@ +#include +#include + +// Template function to convert an object to a string +template +std::string toString(const T& obj) { + std::ostringstream oss; + oss << obj; + return oss.str(); +} diff --git a/lab4/toString_test.cc b/lab4/toString_test.cc new file mode 100644 index 0000000..4e9860f --- /dev/null +++ b/lab4/toString_test.cc @@ -0,0 +1,22 @@ +#include +#include "date.h" // Inkludera Date-klassen från tidigare lösning +#include "toString.h" // Inkludera toString-mallen + +int main() { + // Testa med ett primitivt datatyper + double d = 1.234; + int i = 42; + std::string strDouble = toString(d); + std::string strInt = toString(i); + + std::cout << "Double as string: " << strDouble << std::endl; + std::cout << "Integer as string: " << strInt << std::endl; + + // Testa med Date-klassen + Date today(2023, 12, 11); // Skapa ett datumobjekt + std::string strDate = toString(today); + + std::cout << "Date as string: " << strDate << std::endl; + + return 0; +}