Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • kerdo/cpp-25
1 result
Show changes
Commits on Source (5)
Showing with 2217 additions and 0 deletions
#include <iostream>
#include <random>
#include <sstream>
#include <string>
// Ülesanne 1.
// Kirjuta funktsioon `is_palindrome`, mis kontrollib kas sõne on palindroom
// (https://et.wikipedia.org/wiki/Palindroom). Ignoreeri tühikuid (whitespace)
// ja suurtähelisust, s.t et "a A" on palindroom.
//
// Kasuta funktsioonide üle defineerimist, et funktsioon kasutaks mõlemat
// tüüpi argumente, nii `std::string` kui ka C-tüüpi sõne `const char*`.
// Mõlemad üle defineerimised peavad tagastama tõeväärtustüübi `bool`.
//
// Näide:
// is_palindrome("racecar") tagastab true
// is_palindrome(std::string("racecar")) tagastab true
// is_palindrome("") tagastab true
// is_palindrome("ab") tagastab false
//
// NB! Pane tähele, et ainult `bool is_palindrome(std::string)` loomisel
// töötavad mõlemat tüüpi argumendid. Uuri iseseisvalt lähemalt miks see nii
// juhtub.
bool is_palindrome(const char *s) {
size_t left = 0;
auto length = std::strlen(s);
if (length == 0) {
return true;
}
size_t right = length - 1;
while (left < right) {
if (left < right && std::tolower(s[left]) != std::tolower(s[right])) {
return false;
}
left++;
right--;
}
return true;
}
bool is_palindrome(std::string s) { return is_palindrome(s.c_str()); }
// Ülesanne 2.
// Kirjuta funktsioon `word_count`, mis loeb sõnade arvu antud sõnes.
// Siin kontekstis tähendab sõna tühikutega (whitespace) eraldatud karakterite
// jada. Tühjas sõnes on 0 sõna.
//
// Näide:
// word_count("Tere maailm!") tagastab 2 ja toimib järgmiselt:
// 1. sõna on "Tere"
// 2. sõna on "maailm!"
size_t word_count(std::string s) {
std::stringstream ss(s);
size_t count = 0;
std::string sona;
while (ss >> sona) {
count++;
}
return count;
}
// Ülesanne 3.
// Kirjuta funktsioon `words`, mis tagastab konteineri
// `std::vector<std::string>`, ning tagastab iga sõna antud sõnes. Sõna on siin
// kontekstis sama, mis ülesandes 2.
//
// Näide:
// words("Tere maailm!") tagastab vektori, kus on sõnad "Tere" ja "maailm!"
// words("a b c d ") tagastab vektori, kus on sõnad "a", "b", "c" ja "d"
// Pane tähele, et viimane sõna on "d" mitte "", s.t ignoreeri ülejäänud
// tühikuid
//
// Vihje: Proovi kasutada klassi `std::stringstream`
std::vector<std::string> words(std::string s) {
std::vector<std::string> words;
std::stringstream ss(s);
std::string sona;
while (ss >> sona) {
words.push_back(sona);
}
return words;
}
// Ülesanne 4.
// Kirjuta funktsioon `random_diff`, mis saab argumendiks täisarvu n ning
// konstrueerib 2 täisarvuvektorit (`std::vector<int>`). Seejärel lisab
// funktsioon mõlemasse vektorisse juhuslikke arve senikaua kuni mõlema vektori
// pikkus on n. Vali ise, mille alusel vektoreid täita. Funktsioon tagastab
// vektori vastavate elementide vahede summa. See tähendab, et kui vektor a =
// {1, 2, 3} ja b = {4, 5, 6} siis tagastatakse sum({1 - 4, 2 - 5, 3 - 6}) = -9
// Üleval toodud näidis on pseudokood ja ei ole valiidne C++ keeles.
//
// NB! Ära kasuta funktsiooni `rand()`.
// NB! Ülesannet on võimalik lahendada ilma vektoriteta, aga lahenda siiski
// kasutades vektoreid.
int random_diff(int n) {
std::vector<int> v1(n);
std::vector<int> v2(n);
std::default_random_engine gen;
std::uniform_int_distribution<int> jaotus(-100, 100);
for (int i = 0; i < n; ++i) {
v1[i] = jaotus(gen);
v2[i] = jaotus(gen);
}
std::vector<int> diffs(n);
for (int i = 0; i < n; ++i) {
diffs[i] = v1[i] - v2[i];
}
int sum = 0;
for (int i = 0; i < n; ++i) {
sum += diffs[i];
}
// Võimalus, kuidas vektori elemendid kokku liita
// return std::reduce(diffs.begin(), diffs.end(), 0);
return sum;
}
// Ülesanne 5.
// Kirjuta funktsioon `randomize`, mis saab argumendiks C-tüüpi täisarvude
// massiivi ning selle pikkuse (tüüpi size_t). Seejärel täidab fuktsioon antud
// massiivi juhuslike arvudega ning tagastab nende arvude summa. Võid eeldada,
// et n > 0
//
// Näide:
// int arr[10] = {0};
// size_t size = 10;
// int sum = randomize(arr, size);
// // nüüd on massiivis juhuslikud arvud ja sum võrdne nende summaga
int randomize(int *arr, size_t n) {
auto sum = 0;
std::default_random_engine gen;
std::uniform_int_distribution<int> jaotus(-100, 100);
for (size_t i = 0; i < n; i++) {
auto x = jaotus(gen);
arr[i] = x;
sum += x;
}
return sum;
}
// Ülesanne 6.
// Kirjuta funktsioon `average`, mis saab argumendiks konteineri
// `std::array<int, 10>` ning tagastab nende arvude aritmeetilise keskmise.
//
// Näide:
// std::array<int, 10> arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// average(arr) tagastab keskmise 5.5
//
// NB! Pane tähele, et funktsioon aktsepteerib argumendina ainult `std::array`
// konteinereid mille pikkus on 10. Selle piirangu eemaldamist vaatame tulevatel
// nädalatel.
//
// Mõtle std::array ja C-tüüpi massiivide erisuse peale
float average(std::array<int, 10> arr) {
return std::reduce(arr.begin(), arr.end(), 0) /
static_cast<float>(arr.size());
}
// Ära neid muutujaid ja funktsioone redigeeri.
static int g_TotalTestCount = 0;
static int g_SuccessfulTestCount = 0;
#define TEST(condition) testAssert(condition, __FILE__, __LINE__)
void testAssert(bool condition, const char *file, int line) {
g_TotalTestCount++;
if (!condition) {
std::cout << "VIGA: Kontroll asukohas " << file << ":" << line
<< " ei olnud õige.\n";
} else {
g_SuccessfulTestCount++;
}
}
auto equal(auto a, auto b) {
if (a.size() != b.size()) {
return false;
}
return std::equal(a.begin(), a.end(), b.begin());
}
template <typename T> auto all(T *ptr, size_t count, T comp) {
return std::all_of(ptr, ptr + count, [comp](auto v) { return v == comp; });
}
int main() {
// Ülesanne 1. Palindroom
TEST(is_palindrome("racecar") == true);
TEST(is_palindrome("raCecaR") == true);
TEST(is_palindrome(std::string("racecar")) == true);
TEST(is_palindrome(std::string("")) == true);
TEST(is_palindrome(std::string("aa")) == true);
TEST(is_palindrome(std::string("ab")) == false);
TEST(is_palindrome(std::string("()()")) == false);
TEST(is_palindrome(std::string("())(")) == true);
// Ülesanne 2. Sõnade arv
TEST(word_count("") == 0);
TEST(word_count("A B") == 2);
TEST(word_count("A B ") == 2);
TEST(word_count("Tere maailm!") == 2);
// Ülesanne 3. Sõnad
using StrVector = std::vector<std::string>;
TEST(words("").empty());
TEST(equal(words("A B"), StrVector{"A", "B"}));
TEST(equal(words("A B "), StrVector{"A", "B"}));
TEST(equal(words("a b c d"), StrVector{"a", "b", "c", "d"}));
// Ülesanne 4. Suvalised arvud
// NB! Korrektset tulemust on pisut keerukam testida. Proovi mõelda, kuidas
// sina seda testiksid.
TEST(random_diff(10) != std::numeric_limits<int>::min());
{
int arr[10] = {0};
size_t size = 10;
int sum = randomize(arr, size);
// Statistiliselt ei tohiks summa olla 0.
TEST(sum > 0);
// Arvud ei tohiks olla 0.
TEST(!all(arr, size, 0));
}
// Ülesanne 5. Keskmine väärtus
{
std::array arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
TEST(average(arr) == 5.5f);
}
std::cout << "Testide tulemus: " << g_SuccessfulTestCount << "/"
<< g_TotalTestCount << '\n';
return 0;
}
#include <cassert>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <print>
#include <sstream>
#include <utility>
// 0. Ülesanne
// Kirjuta main-funktsioon, signatuuriga `int main(int argc, char **argv)` ning
// konstrueeri vastavad konteinerid argumentide jaoks, et saaks välja kutsuda
// funktsiooni `MinuMain` (vt signatuuri all). NB! Funktsiooni `MinuMain`
// signatuuri ära muuda! Selle olemasolu ja korrektsust kontrollib rida
// `static_assert(MinuMainKontroll<>);`
// Mida see rida täpselt teeb, vaatame järgnevatel nädalatel.
//
// Funktsiooni `main` mall on täiesti faili alguses. Ära seda mujale liiguta,
// oma lahendus kirjuta sinna sisse.
// Järgnev rida lubab meil `std::filesystem` asemel kasutada `fs`, mis teeb
// koodi mugavalt lihtsamaks.
namespace fs = std::filesystem;
// Kodutöö ZIP-arhiivis on kaasa pakitud kaust `failid`. Vaata, et see oleks su
// projektis kätte saadav.
//
// Iga programmi käivitamisel pannakse see käima mingist kindlast kaustast. Seda
// kausta kutsutakse "working directory". Uuri lähemalt:
// https://en.wikipedia.org/wiki/Working_directory.
//
// CLionis programmi jooksutamisel on working directory reeglina
// `cmake-build-debug`, mille olemasolu ja haldamisega tegeleb tööriist CMake.
//
// Kui tahame failiteed viidata ZIP-arhiivis olevale kaustale `failid`, siis
// peame minema ühe kausta võrra tagasi (`..`) ja siis saame ligipääsu antud
// kaustale.
//
// Failipuu näide:
// cmake-build-debug/
// ... <- siin kaustas on kuskil programm ise (reeglina nimega, mis on
// määratud CMakeLists.txt failis)
// seega programmi käivitamisel on working directory just see kaust
// `cmake-build-debug/`
// failid/
// ...
// main.cpp <- see fail
// CMakeLists.txt
//
// Kui jooksutad programmi kuidagi teisiti, muuda allolevat muutujat vastavalt
// nii, et see viitaks antud kaustale.
const auto FailidDir = fs::path("../failid");
// 1. Ülesanne
//
// Kirjuta funktsioon `readFileUntil(fs::path, long n)`, mis loeb faili ja
// tagastab selle sisu vastavalt järgmistele reeglitele:
// 1. Kui tegemist on tekstifailiga (laiendiga .txt, .sql, .cpp jne), siis
// tagastab funktsioon ülimalt `n` karakterit faili algusest.
// Kui faili sisu on lühem, siis on sõne pikkus sama, mis karakterite arv
// failis.
// 2. Kui tegemist on binaarfailiga (fail, millel pole faililaiendit), siis
// loeb failist sisu bait-haaval ning lõpetab
// töö, kui leiab funktsioonist väärtuse 0. Funktsioon tagastab baitide
// numbrilised väärtused sõnena. See tähendab, et kui failis on baidid `4 15
// 0`, tagastab funktsioon `4150`. ZIP-arhiivis kaasa antud failide kohta
// leiad testid alt.
// Võid eeldada, et funktsioon kutsutakse välja ainult failidel, s.t kaustade
// korral ei pea funktsioon midagi tegema. Funktsioon peab samuti kontrollima
// faili avamisel selle olemasolu. Kui faili ei leidu, tagastab funktsioon tühja
// sõne.
std::string readFileUntil(const fs::path &path, const long n) {
std::ifstream f(path);
assert(f);
std::vector<char> buf(n);
f.read(buf.data(), n);
auto fPos = path.string().rfind('/');
auto fileName = path.string().substr(fPos);
if (fileName.find('.') == std::string::npos) {
// Eelduste järgi binaarfail
int last = -1;
std::stringstream ss;
for (auto c : buf) {
if (last == 0 && c == 0) {
break;
}
last = static_cast<int>(static_cast<unsigned char>(c));
ss << std::hex << last;
}
return ss.str();
}
return {buf.data()};
}
// Defineeri üks tüübinimi (alias, vihje: `using`), mis vastaks sõnepaarile.
// Selle kohta uuri klassi `std::pair<T, U>`:
// https://en.cppreference.com/w/cpp/utility/pair. `StrPair paar{"a", "b"};`
// peaks olema valiidne C++ kood.
using StrPair = std::pair<std::string, std::string>;
// Allolev kood kontrollib, kas määrasid tüübialiase õigesti
template <typename T = std::string>
concept StrPairKontroll = requires(T a, T b) {
{ StrPair{a, b} };
};
static_assert(StrPairKontroll<>);
// 2. Ülesanne
// Kirjuta funktsioon `readPairs(fs::path path)`, mis loeb ja töötleb antud
// faili rea kaupa. Iga rida, mis on kujul "<sõna> ja <sõna>" on valiidne rida,
// tuleb konstrueerida sõnapaariks (vt üleval defineeritud tüübialiast) ning
// lisada vektorisse. Faili sisu lõppedes tagastab funktsioon paaride vektorid.
// Funktsioon võib eeldada, et fail eksisteerib ning antud asukoht ei viita
// kaustale. Funktsioon võib eeldada, et failis on vähemalt üks rida, mis EI
// pruugi olla õiges formaadis.
//
// Lisa: Uuri lähemalt klassi `std::vector` liikmefunktsiooni `emplace_back`.
// Selle kasutamine ei ole kohustuslik, aga äkki on selles ülesandes
// 'efektiivsem'?.
std::vector<StrPair> readPairs(const fs::path &path) {
std::ifstream f(path);
assert(f);
std::vector<StrPair> pairs{};
std::string rida;
while (std::getline(f, rida)) {
auto const jaIt = rida.find(" ja ");
if (jaIt == std::string::npos) {
continue;
}
const auto start = rida.substr(0, jaIt);
const auto end = rida.substr(jaIt + 4);
if (!start.empty() && !end.empty()) {
pairs.emplace_back(start, end);
}
}
return pairs;
}
// 3. Ülesanne
// Kirjuta funktsioon `walk(fs::path)`, mis käitub järgmiselt:
// Kui tegemist on kausta asukohaga (s.t path viitab kaustale), siis väljastab
// kausta asukoha lõpuga "/" ning
// käitub kausta alamfailide ja -kaustade korral täpselt samamoodi nagu
// kirjeldatud (vihje: rekursioon). Pane tähele, et järjekord ei ole
// oluline.
// Kui tegemist on faili asukohaga, siis väljastab faili 10 esimest karakterit
// (kasuta funktsiooni `readFileUntil`.
//
// Näide: Vaata kodutöö ZIP-arhiivis kaasa antud kausta `failid`.
// Selle funktsiooni välja kutsumisel argumendiga `FAILID_DIR` (vt makro
// kommentaare!) peaks olema tulemus umbes järgnev:
// ../failid/
// ../failid/b.sql -> 'SELECT * F'
// ../failid/a.txt -> 'see on fai'
// ../failid/c/
// ../failid/c/d.cpp -> '#include <'
// ../failid/c/f.txt -> 'lyhike'
// ../failid/c/e -> '69420'
// ../failid/paarid.txt -> 'must ja va'
//
// NB! Pane tähele, et olenevalt sellest, kas jooksutad programmi läbi CLioni,
// käsurealt või mingis muus keskkonnas, võib olla kausta failid asukoht
// relatiivse path'ina erinev. Loe täpsemalt kommentaari muutuja `FailidDir`
// juures.
void walk(const fs::path &path) {
if (is_directory(path)) {
std::cout << path.string() << "/\n";
const auto it = fs::directory_iterator(path);
for (const auto &entry : it) {
walk(entry.path());
}
} else {
auto content = readFileUntil(path, 10);
std::cout << path.string() << " -> '" << content << "'\n";
}
}
// 4. Ülesanne
// Loo `enum class`, mis võib olla 2 väärtust: `File` või `Dir`.
// Näide:
// FileType::File
// FileType::Dir
// mõlemad read peavad olema valiidsed C++ keeles.
// Lisaks vaata, et järjekord oleks õige (selle kohta ka kontroll paar rida
// allpool). Seejärel kirjuta funktsioon `fileType`, mis võtab argumendiks
// `fs::path` objekti ning tagastab, kas antud failitee viitab failile või
// kaustale. Vastavalt `FileType::File` ja `FileType::Dir`. Funktsiooni
// signatuuris ei tohi olla kirjas võtmesõna `auto`, s.t `auto fileType(...)` ei
// ole lubatud. Uuri, mis on vahet `enum` ja `enum class` konstruktsioonidel.
// Miks peaks eelistama üht üle teise? NB! Pane tähele, et viimasele küsimusele
// vastamine on ülesande läbimiseks kohustuslik, s.t on väärt punkte.
enum class FileType {
File,
Dir,
};
static_assert(static_cast<int>(FileType::File) == 0);
static_assert(static_cast<int>(FileType::Dir) == 1);
FileType fileType(const fs::path &path) {
if (is_directory(path)) {
return FileType::Dir;
}
return FileType::File;
}
// 5. Ülesanne
// Kirjuta funktsioon `writeFile`, mis võtab 3 argumenti: failitee (fs::path),
// sõne (std::string) ning lipu `shouldAppend` (bool), mille algväärtus on
// `false`. Funktsioon kirjutab antud sõne antud failiteel asuvasse faili.
// Funktsioon peab faili puudumisel selle looma.
// Juhul kui kolmas argument `shouldAppend` on `true`, ei tohi funktsioon
// failisisu üle kirjutada, vaid peab antud sõne lisama faili lõppu. Vastasel
// juhul peab funktsioon faili sisu üle kirjutama.
//
// Näide:
// writeFile("a.txt", "tere") kirjutab faili "a.txt" sisu "tere", funktsioon
// kirjutab sisu üle writeFile("a.txt", "tere", true) kirjutab faili "a.txt"
// sisu "tere", funktsioon ei kirjuta sisu üle
void writeFile(const fs::path &path, const std::string &content,
bool shouldAppend = false) {
std::ios_base::openmode mode = std::ios::out;
if (shouldAppend) {
mode |= std::ios::app;
}
std::ofstream file(path, mode);
file << content;
}
// [Lisaülesanne] 6. Ülesanne
// All on antud paar `static_assert` kontrolli. Kõik neist töötavad
// kompilaatoriga "Apple Clang 16.0" standardiga C++20. Implementeeri
// kompileerimisaegsed funktsioonid nii, et võimalikult palju neist
// `static_assert` testidest töötaksid korrektselt. Pane tähele, et mõni avaldis
// ei pruugi üldse teise kompilaatoriga olla lahendatav (s.t ei toeta
// kompileerimisaegseid avaldisi). Uuri, millised konstruktsioonid selliseid
// avaldisi toetavad. Vihje: võtmesõna `constexpr`. NB! `static_assert` kutseid
// muutma ei pea! Implementeeri antud funktsioon või jäta kommenteerituks.
constexpr long long int factorial(const long long int n) {
if (n <= 1) {
return n;
}
return n * factorial(n - 1);
}
constexpr std::array<int, 2> getArray() { return {1, 2}; }
constexpr std::vector<int> getVector() { return {1, 2}; }
constexpr std::string getString() { return "tere"; }
static_assert(factorial(1) == 1);
static_assert(factorial(2) == 2);
static_assert(factorial(3) == 6);
static_assert(std::is_same_v<decltype(getArray()), std::array<int, 2>>);
static_assert(getArray().size() == 2);
static_assert(getArray() == std::array{1, 2});
static_assert(std::is_same_v<decltype(getVector()), std::vector<int>>);
static_assert(getVector().size() == 2);
static_assert(std::is_same_v<decltype(getString()), std::string>);
static_assert(getString() == "tere");
// Ära neid muutujaid ja funktsioone redigeeri.
static int g_TotalTestCount = 0;
static int g_SuccessfulTestCount = 0;
#define TEST(condition) testAssert(condition, __FILE__, __LINE__)
void testAssert(bool condition, const char *file, int line) {
g_TotalTestCount++;
if (!condition) {
std::cout << "VIGA: Kontroll asukohas " << file << ":" << line
<< " ei olnud õige.\n";
} else {
g_SuccessfulTestCount++;
}
}
template <typename T> auto equal(const T &a, const T &b) {
if (a.size() != b.size()) {
return false;
}
return std::equal(a.begin(), a.end(), b.begin());
}
int MinuMain(const std::vector<std::string> &args) {
// Igal programmil on alati vähemalt üks argument – tema ise. Uuri lähemalt
// iseseisvalt.
TEST(!args.empty());
// Kontrollime, et oled kausta `failid` õigesse kohta pannud ja muutuja
// `FailidDir` õigesti väärtustanud.
TEST(fs::is_directory(FailidDir));
// Allolev rida väljastab su töökausta ("working directory"):
std::cout << "Programm jookseb kaustas " << fs::current_path() << '\n';
// Ülesanne 1. Failide lugemine
TEST(readFileUntil(FailidDir / "a.txt", 10).size() == 10);
TEST(readFileUntil(FailidDir / "a.txt", 10) == "see on fai");
TEST(readFileUntil(FailidDir / "c" / "f.txt", 10).size() == 6);
TEST(readFileUntil(FailidDir / "c" / "f.txt", 10) == "lyhike");
TEST(readFileUntil(FailidDir / "c" / "e", 10) == "69420");
// Ülesanne 2. Faili lugemine ridade kaupa, `std::pair`
{
// ZIP-arhiivis kaasa antud fail peaks andma järgneva tulemuse
const auto pairs = readPairs(FailidDir / "paarid.txt");
const std::vector<StrPair> expectedPairs = {
{"must", "valge"}, {"sinine", "punane"}, {"1", "2"}};
TEST(equal(pairs, expectedPairs));
}
// Ülesanne 3. Failipuu kõndimine
// Funktsioon peaks töötama korrektselt ZIP-arhiivis antud kaustaga `failid`.
// Vaata näidet ülesande püstituse juures
walk(FailidDir);
// Ülesanne 4. `enum class`
TEST(fileType(FailidDir) == FileType::Dir);
TEST(fileType(FailidDir / "a.txt") == FileType::File);
// Ülesanne 5. Faili kirjutamine
{
// Ära seda test-koodi modifitseeri.
// Siin on pisut trikke, et Sulle mitte lahendus päris ette öelda
auto const fName = fs::path("test.txt");
copy_file(FailidDir / "a.txt", fName);
auto fileSize = [fName] {
return static_cast<long long int>(
std::ifstream(fName, std::ifstream::ate | std::ifstream::binary)
.tellg());
};
try {
const auto origSize = fileSize();
writeFile(fName, "tere", true);
TEST(fileSize() > origSize);
writeFile(fName, "tere");
TEST(fileSize() == std::string{"tere"}.size());
} catch (std::exception &) {
std::cout << "Programm krahhis! Üritan parandada algset seisundit!\n";
}
fs::remove(fName);
}
std::cout << "Testide tulemus: " << g_SuccessfulTestCount << "/"
<< g_TotalTestCount << '\n';
return g_SuccessfulTestCount != g_TotalTestCount;
}
template <typename T = std::string>
concept MinuMainKontroll = requires(std::vector<T> &a) {
{ MinuMain(a) } -> std::same_as<int>;
};
static_assert(MinuMainKontroll<>);
int main(const int argc, char **argv) {
std::vector<std::string> args(argc);
for (int i = 0; i < argc; i++) {
args[i] = argv[i];
}
return MinuMain(args);
}
#include <algorithm>
#include <cassert>
#include <concepts>
#include <iostream>
#include <numeric>
#include <print>
#include <sstream>
#include <type_traits>
// 1. Ülesanne
// Siin on antud põhi klassile `IntVector`, mis on sisuliselt "wrapper"
// standardteegi vektorile, mis hoiustab täisarve. Implementeeri kõik nõutud
// konstruktorid ja liikmefunktsioonid.
class IntVector {
public:
// Vaikekonstruktor, mis initsialiseerib tühja vektori
IntVector() = default;
// Konstruktor, mis initsialiseerib n-pikkuse vektori vaikeväärtusega 0
// Näide:
// IntVector v(n) peaks väärtustama liikme `m_Vec` nagu teeb seda
// `std::vector<int> v(n)`
// IntVector objekti ei tohiks saada konstrueerida järgmiselt: `IntVector v =
// n` - see ei ole korrektne.
explicit IntVector(const long n) : m_Vec(n) {}
// Konstruktor, mis initsialiseerib n-pikkuse vektori väärtusega v
// Näide:
// IntVector v(n, v) peaks väärtustama liikme `m_Vec` nagu teeb seda
// `std::vector<int> v(n, v)`
IntVector(const long n, const int val) : m_Vec(n, val) {}
// "Range" konstruktor, mis käitub kui Pythoni `range` funktsioon.
// Konstruktor on 3 argumendiga: start, end, step ning lisab vektorisse
// elemendid vahemikus [start, end) sammuga `step`. Näide:
// IntVector v(0, 10, 2) lisab vektorisse elemendid 0, 2, 4, 6 ja 8
// IntVector v(0, 2, 1) lisab vektorisse elemendid 0 ja 1
IntVector(const int begin, const int end, const int step) {
for (auto i = begin; i < end; i += step) {
m_Vec.push_back(i);
}
}
// `set(size_t i, int val)` liikmefunktsioon, mis lisab elemendi `val`
// vektoris kohale `i`. Pane tähele, et kui vektori suurus on väiksem kui i,
// siis ära elementi lisa. Liikmefunktsioon tagastab tõeväärtuse, kas element
// lisati
bool set(size_t i, int val) {
if (m_Vec.size() <= i) {
return false;
}
m_Vec[i] = val;
return true;
}
// `int sum()` liikmefunktsioon tagastab elementide summa
[[nodiscard]] int sum() {
return std::accumulate(m_Vec.begin(), m_Vec.end(), 0);
}
// `int min()` liikmefunktsioon tagastab minimaalse elemendi
// Elementide puudumise korral tagastada 0
int min() {
return std::min_element(m_Vec.begin(), m_Vec.end()) - m_Vec.begin();
}
// `int max()` liikmefunktsioon tagastab minimaalse elemendi
// Elementide puudumise korral tagastada 0
int max() {
return std::max_element(m_Vec.begin(), m_Vec.end()) - m_Vec.begin();
}
// Kuna `m_Vec` on privaatne, siis on testimiseks hea, kui saame selle
// klassist välja ka anda. Selleks teeme getter-funktsiooni. Pane tähele, et
// antud liikmefunktsioon ei tee vektorist koopiat – selle olulisusest räägime
// järgmisel nädalal.
[[nodiscard]] auto &vec() const { return m_Vec; }
// Liikmefunktsioonid `begin` ja `end` on vajalikud for-of tsükli
// kasutamiseks. Ära neid siin modifitseeri
[[nodiscard]] auto begin() const { return m_Vec.begin(); }
[[nodiscard]] auto end() const { return m_Vec.end(); }
[[nodiscard]] auto begin() { return m_Vec.begin(); }
[[nodiscard]] auto end() { return m_Vec.end(); }
private:
std::vector<int> m_Vec{};
};
// 2. Ülesanne
// Kirjuta funktsioonimall `sum(T)`, millel on tüübiparameeter T.
// Funktsioonimall tagastab antud konteineri summa. Võid eeldada, et T on alati
// konteiner ehk saad kasutada for-of tsüklit ning et konteiner hoiab endas
// int-tüüpi elemente. NB! Ära kasuta kuskil siin funktsioonimallis võtmesõna
// `auto`
template <typename T> int sum(const T &t) {
return std::accumulate(std::begin(t), std::end(t), 0);
}
// 3. Ülesanne
// Kirjuta funktsioonimall `negate(T)`, millel on tüübiparameeter T. Piira
// parameetrit T nii, et funktsioon oleks kutsutav ainult täisarvude ning
// ujukoma arvudega. Selleks uuri võtmesõna requires ning päist <concepts>.
// Funktsioon ise tagastab antud arvu vastandarvu, s.t 1 -> -1, 0 -> 0, -1 -> 1
// jne. NB! Ära kasuta kuskil siin funktsioonimallis võtmesõna `auto`
template <typename T>
requires std::integral<T> || std::floating_point<T>
T negate(T t) {
return -t;
}
// Ära seda funktsioonimalli modifitseeri!
template <typename T> bool testNegateType() {
return requires(T t) { negate(t); };
}
// 4. Ülesanne [kuni 0.3 lisapunkti]
// Kirjuta funktsioonimall `variadicSum`, mida saab välja kutsuda ükskõik mis
// arvu argumentidega. Võid eeldada, et argumentide tüübid on samad.
template <typename... T> auto variadicSum(T... args) { return (... + args); }
// 5. Ülesanne [kuni 0.5 lisapunkti]
// Kirjuta klass `MinuMap`, mis simuleerib `HashMap` või `dict`-tüüpi
// andmestruktuuri. Olgu klassil liikmefunktsioonid:
// set(std::string key, T value)
// std::optional<T> get(std::string key)
// bool exists(std::string key)
// void clear()
// void print()
// bool empty()
//
// 1. Pane tähele, et liikmefunktsioon `get` tagastab tühja std::optional<T>
// objekti (või std::nullopt) kui soovitud võtmega elementi ei leidu.
// 2. Pane tähele, et liikmefunktsioon `print` peab väljastama antud formaadis:
// <key1> -> <value1>
// <key2> -> <value2>
// Pane tähele, et iga rea lõpus peab olema reavahetus (vt ka testi
// main-funktsioonis).
// 3. Võid eeldada, et klass peab töötama ainult antud testide piires (vt
// main-funktsioon). Küll aga ei tohi lahendust hard-code'ida.
//
// Vihje: Elementide hoidmiseks võib abiks olla klass `std::pair<std::string,
// T>`. Lisaks uuri klassi `std::optional<T>`. NB! Ära kasuta standardteegi
// klassi `std::map`! Hästi sobib näiteks vektor ;)
template <typename T> class MinuMap {
public:
MinuMap() = default;
void set(std::string key, T value) {
m_Map.emplace_back(std::move(key), std::move(value));
}
std::optional<T> get(std::string key) {
auto it = std::find_if(m_Map.begin(), m_Map.end(),
[&key](auto &pair) { return pair.first == key; });
if (it == m_Map.end()) {
return std::nullopt;
}
return it->second;
}
bool exists(std::string key) {
auto it = std::find_if(m_Map.begin(), m_Map.end(),
[&key](auto &pair) { return pair.first == key; });
return it != m_Map.end();
}
void clear() { m_Map.clear(); }
void print() {
for (const auto &[key, value] : m_Map) {
std::cout << key << " -> " << value << '\n';
}
}
bool empty() { return m_Map.empty(); }
private:
std::vector<std::pair<std::string, T>> m_Map;
};
// Ära neid muutujaid ja funktsioone redigeeri.
static int g_TotalTestCount = 0;
static int g_SuccessfulTestCount = 0;
#define TEST(condition) testAssert(condition, __FILE__, __LINE__)
void testAssert(bool condition, const char *file, int line) {
g_TotalTestCount++;
if (!condition) {
std::cout << "VIGA: Kontroll asukohas " << file << ":" << line
<< " ei olnud õige.\n";
} else {
g_SuccessfulTestCount++;
}
}
template <typename T> auto equal(const T &a, const T &b) {
if (a.size() != b.size()) {
return false;
}
return std::equal(a.begin(), a.end(), b.begin());
}
auto all(const auto &c, const auto val) {
return std::all_of(c.begin(), c.end(),
[&val](const auto &v) { return v == val; });
}
class CoutCapture {
std::stringstream &m_Buffer;
std::streambuf *m_Prevcout;
public:
explicit CoutCapture(std::stringstream &buf) : m_Buffer(buf) {
m_Prevcout = std::cout.rdbuf(m_Buffer.rdbuf());
}
~CoutCapture() { std::cout.rdbuf(m_Prevcout); }
};
int main() {
// 1. ülesanne. IntVector
{
// Vaikekonstruktor
IntVector v;
TEST(v.vec().empty());
}
{
// Ühe argumendiga konstruktor
IntVector v(10);
TEST(v.vec().size() == 10);
TEST(all(v, 0));
}
{
// Kahe argumendiga konstruktor
IntVector v(10, 1);
TEST(v.vec().size() == 10);
TEST(all(v, 1));
}
{
// "Range" konstruktor
IntVector v(0, 10, 2);
TEST(equal(v.vec(), {0, 2, 4, 6, 8}));
}
{
// `set` liikmefunktsioon
IntVector v(10);
TEST(v.set(0, 1));
TEST(!v.set(11, 1));
v = IntVector(0);
TEST(!v.set(0, 1));
}
{
// `sum` liikmefunktsioon
IntVector v(10);
TEST(v.sum() == 0);
v = IntVector(10, 1);
TEST(v.sum() == 10);
}
{
// `min` ja `max` liikmefunktsioonid
IntVector v(0, 10, 1);
TEST(v.min() == 0);
TEST(v.max() == 9);
v = IntVector(0);
TEST(v.min() == 0);
TEST(v.max() == 0);
}
// 2. ülesanne. Funktsioonimall `sum`
{
std::vector v{1, 2, 3, 4, 5};
// Pane tähele, et antud funktsioonimalli peab saama kutsuda kahel viisil:
TEST(sum(v) == 15);
TEST(sum<std::vector<int>>(v) == 15);
std::array<int, 5> a{1, 2, 3, 4, 5};
TEST(sum(a) == 15);
}
// 3. ülesanne. Funktsioonimall `negate`
{
TEST(negate(1) == -1);
TEST(negate(-1) == 1);
TEST(negate(0) == 0);
TEST(testNegateType<int>());
TEST(testNegateType<float>());
TEST(testNegateType<double>());
TEST(!testNegateType<std::string>());
}
// Kui testid on siiani läbitud, on ülesanne lahendatud
// Siit edasi on lisaülesanded
// 4. ülesanne. Variadic funktsioonimall
#if 0
{
TEST(variadicSum(1, 2, 3, 4, 5) == 15);
TEST(variadicSum(0) == 0);
TEST(variadicSum(1) == 1);
}
#endif
// 5. ülesanne. Klassimall, hashmap'i "emulatsioon"
#if 0
{
MinuMap<int> map{};
TEST(map.empty());
map.set("tere", 1);
TEST(!map.empty());
TEST(map.get("tere") == std::optional(1));
TEST(map.get("tere").value() == 1);
TEST(!map.get("maailm"));
map.set("maailm", 2);
TEST(map.get("maailm").value() == 2);
TEST(map.exists("tere"));
TEST(map.exists("maailm"));
TEST(!map.exists("koer"));
std::stringstream buf;
if (CoutCapture c(buf); true) {
map.print();
}
auto str = buf.str();
TEST(str == "tere -> 1\nmaailm -> 2\n");
map.clear();
TEST(map.empty());
}
#endif
std::cout << "Testide tulemus: " << g_SuccessfulTestCount << "/"
<< g_TotalTestCount << '\n';
return g_SuccessfulTestCount != g_TotalTestCount;
}
#pragma once
#include <cstdlib>
// 2. ülesanne. Kirjuta funktsioonimall `T *generate(size_t)`, mis allokeerib kuhimälus massiivi antud pikkusega
// ning täidab selle juhuslike arvudega. Juhuslike arvude genereerimisel ära kasuta funktsiooni `rand`.
// Piira malliparameetrit nii, et malli saab kasutada ainult täisarvude ja ujukomaarvudega (vt selleks 4. nädala materjali).
// Juhul kui argumendi väärtus on 0, tagastab funktsioon NULL-viida (`nullptr`).
// NB! Ära C++-s kasuta võtmesõna `NULL`. See on C-keelest "jäänud".
template<typename T>
requires std::integral<T> || std::floating_point<T>
T *generate(size_t size) {
if (size == 0) {
return nullptr;
}
auto ptr = new T[size];
for (size_t i = 0; i < size; i++) {
ptr[i] = rand() % 100;
}
return ptr;
}
// Ära seda modifitseeri
template<typename T>
bool testGenerateType() {
return requires (T t) { generate<T>(0); };
}
#pragma once
template<typename T, typename U>
class MinuPaar {
public:
T first;
U second;
MinuPaar() requires std::is_default_constructible_v<T> && std::is_default_constructible_v<U> = default;
MinuPaar(T fst, U snd) : first(fst), second(snd) {}
};
#pragma once
#include <utility>
// 3. ülesanne. Implementeeri klassimall `MinuVektor<T>`, mis käitub vastavalt kirjeldusele. Enamjaolt peaks klassimall käituma
// sarnaselt `std::vector<T>` mallile.
//
// Vektor peab dünaamiliselt suurenema elementide lisamisel. Vali ise sobiv algoritm suurendamiseks.
// Naiivne lahendus puhvri täitumisel seda 2x suurendada on OK.
//
// Vektoril on 3 konstruktorit:
// 1. Vaikekonstruktor, mis allokeerib vektori siseselt puhvri suurusega 2.
// 2. Konstruktor, mille argumendiks on üks `size_t` arv, mis allokeerib puhvri antud suurusega.
// 3. Konstruktor, mille argumentideks on `size_t` ja `T`, mis allokeerib puhvri antud suurusega ja väärtustab
// kõik puhvri elemendid antud tüübi T väärtusega.
// Vektoril on destruktor, mis vabastab allokeeritud puhvri.
//
// NB! Kõik konstruktorid peavad allokeerima "alloleva" puhvri.
//
// Vektoril on järgmised avalikud liikmefunktsioonid:
// - `void push_back(T)`, mis paneb antud väärtuse vektori lõppu.
// - `std::optional<T> get(size_t)`, mis tagastab "optional" elemendi asukohal, mis on antud funktsiooni
// argumendina. Kui sellisel kohal elementi ei ole (nt üritatakse lugeda väärtust vektorist väljas), tagastada
// tühi `std::optional` (`std::nullopt`).
// - `size_t size()`, mis tagastab vektoris olevate elementide arvu. Pane tähele, et ei ole korrektne tagastada
// alloleva puhvri suurust.
// - `size_t capacity()`, mis tagastab puhvrisse mahtuvate elementide arvu.
// - `const T *buf() const`, mis tagastab alloleva puhvri. See funktsioon on testimiseks.
// - `std::optional<T> front()`, mis tagastab elemendi vektori alguses. Pane tähele, et see peab käituma
// sarnaselt liikmefunktsioonile `get(size_t)` ehk tagastama tühja `std::optional` objekti juhul kui ei ole
// võimalik esimest elementi tagastada. Mõtle, millal selline olukord on võimalik.
// - `std::optional<T> back()`, mis on ideelt sama nagu `front()` aga tagastab vektori viimase "optional" elemendi.
//
// Vektoril on järgmised avalikud operaatorid:
// - `T operator[](size_t)` mis tagastab väärtuse argumendi kohal. Sisuliselt on tegemist liikmefunktsiooniga `get`,
// aga siin võid eeldada, et antud kohal väärtus eksisteerib.
// - Väljundoperaator, et saaks vektori väljastada järgmiselt: `std::cout << v << '\n';`.
// Formaat peaks olema järgnev: "{x, y, z, ..., w}", kus elemendid x, y, z, ... ja w on vektori elemendid.
// Vihje: Väljundoperaatori korrektne loomine käib pisut teistmoodi kui teiste operaatoride loomine. Uuri iseseisvalt,
// kuidas seda teha.
//
// Lisaülesanne 1. (kuni 0.2 punkti)
// Loo klassile `MinuVektor<T>` staatiline funktsioon `iota(size_t)`, mis tagastab uue vektori antud pikkusega, mille
// elemendid on [0, 1, ..., n - 1], kus `n` on funktsiooni argument.
// Samuti peab funktsioonil olema piirang, et seda saab kutsuda vaid tüübiparameetritega, mis on täisarvutüüpi (0.1 punkti).
//
// Lisaülesanne 2. (kuni 0.3 punkti)
// Loo klassile `MinuVektor<T>` liikmefunktsioon `MinuVektor<U> transform(auto fn)` (antud argument on täpsemalt tüüpi
// `std::function<U(T &)> fn`, kasutame seal auto, et oleks kergem). Funktsioon konstrueerib uue vektori malliparameetriga
// `U` ning transformeerib iga käesoleva vektori elemendi antud funktsiooni alusel.
// Sisuliselt transformeerib ("mapib") funktsioon vektori elemendid antud funktsiooni alusel.
// NB! Pane tähele, et klassimalli pead tervenisti deklareerima ja defineerima (implementeerima) päisefailis.
// Lisaks on reeglina klassimalli liikmefunktsioone lihtsam implementeerida klassideklaratsiooni sees, mitte väljas.
// Vt näide failis `MinuPaar.hpp`
template<typename T>
class MinuVektor {
public:
MinuVektor() {
m_Buffer = new T[m_Capacity];
}
explicit MinuVektor(size_t capacity)
: m_Capacity(capacity) {
m_Buffer = new T[capacity];
}
MinuVektor(size_t capacity, T initial)
: m_Capacity(capacity) {
m_Buffer = new T[capacity];
m_Size = capacity;
for (size_t i = 0; i < m_Capacity; i++) {
m_Buffer[i] = initial;
}
}
~MinuVektor() {
delete[] m_Buffer;
}
void push_back(T value) {
if (m_Size >= m_Capacity) {
resize(m_Capacity * 2);
}
m_Buffer[m_Size++] = value;
}
std::optional<T> get(size_t idx) {
if (idx >= m_Size) {
return std::nullopt;
}
return m_Buffer[idx];
}
std::optional<T> front() {
return get(0);
}
std::optional<T> back() {
return get(m_Size - 1);
}
T operator[](size_t idx) {
return *get(idx);
}
template<typename U>
requires std::integral<U>
static MinuVektor<U> iota(size_t sz) {
MinuVektor v(sz);
for (size_t i = 0; i < sz; i++) {
v.push_back(i);
}
return v;
}
template<typename U>
MinuVektor<U> transform(auto fn) const {
MinuVektor<U> v(size());
for (auto i = 0u; i < size(); i++) {
v.push_back(fn(m_Buffer[i]));
}
return v;
}
[[nodiscard]] const T *buf() const { return m_Buffer; }
[[nodiscard]] auto size() const { return m_Size; }
[[nodiscard]] auto capacity() const { return m_Capacity; }
private:
void resize(const size_t newCapacity) {
if (newCapacity < m_Capacity) {
return;
}
T *newBuffer = new T[newCapacity];
for (size_t i = 0; i < m_Capacity; i++) {
newBuffer[i] = m_Buffer[i];
}
delete[] m_Buffer;
m_Buffer = newBuffer;
m_Capacity = newCapacity;
}
private:
size_t m_Size{0};
size_t m_Capacity{2};
T *m_Buffer = nullptr;
};
template<typename T>
std::ostream &operator<<(std::ostream &os, MinuVektor<T> &v) {
os << '{';
for (size_t i = 0; i < v.size(); i++) {
os << v[i];
if (i != v.size() - 1) {
os << ", ";
}
}
os << '}';
return os;
}
#pragma once
// Pane tähele, et faililaiend `hpp` on reeglina viiteks sellele, et on tegemist
// C++ päisefailiga. Sageli kasutatakse siiani `.h` faililaiendit, mis ei ole tehniliselt
// vale, aga võib tekitada arusaamatusi nendest tulenevaid kompileerimisvigu.
// Samuti ei ole oluline, kuidas faile nimetad. C++ ei piira seda, et ühes failis peab olema üks klass
// või et failinimi peab olema klassi nimi jne. Nimeta oma failid alati nii, kuidas ülesandes nõutud või selle puudumisel
// kuidas ise soovid ja loogiline on. Küll aga ära pane kõike ühte faili :^)
#include <cstdlib>
#include <string>
// "Tavalise" klassi saame implementeerida nii:
// Deklaratsioon on päisefailis
// Definitsioon (implementatsioon) on vastavas lähtekoodifailis (vt String.cpp)
class String {
public:
String();
explicit String(const char *s);
~String();
// Pane tähele, et klasside sees saab ka
// kasutada funktsioonide üledefineerimist
void append(const char *s);
void append(char c);
void append(const String &s);
// Pane tähele, et kuna see liikmefunktsioon tagastab andmetest
// std::string objekti, siis võib see olla `const`.
[[nodiscard]] auto str() const -> std::string;
// Siin on lihtsaks võrdlemiseks 2 võrdlusoperaatorit nii
// sõneklassi endaga kui ka const char* "alumise" tüübiga
bool operator==(const String &other) const;
bool operator==(const char *other) const;
// Üherealisi get-set liikmefunktsioone ei ole võib-olla isegi
// mõtet cpp-faili panna. Reeglina need väga tihti ei muutu ja
// seepärast võib need deklareerida ja defineerida otse
// klassi deklaratsiooni sees
[[nodiscard]] auto size() const { return m_Size; }
[[nodiscard]] const char *data() const { return m_Buffer; }
private:
// Reeglina me ei taha, et keegi väljastpoole sõne pikkust
// ise muudaks. Seega on antud liikmefunktsioon privaatne.
void resize(size_t newCapacity);
// Pane tähele ka seda, et `private`, `public` ja `protected`
// sektsioone võib defineerida läbisegi, nii kuidas vaja.
// Sageli on hea näidata, et siin on uus "plokk" privaatseid
// liikmeid, olgu need siis muutujad või funktsioonid.
// Lõpuks väga vahet ei ole, kuidas klassi struktuuri siin
// failis üles ehitad.
private:
// Pane tähele muutujate järjekorda. Siin on väga oluline tähelepanek
// see, et muutujad initsialiseeritakse selles järjekorras, milles
// nad siin on defineeritud. S.t kõigepealt väärtustatakse m_Size = 0,
// siis m_Capacity = 10 jne. See määrab ka initsialiseerimise nimekirjas
// (initializer list) väärtustamise järjekorra. Uuri ja mängi sellega
// ringi iseseisvalt.
size_t m_Size{0};
size_t m_Capacity{10};
char *m_Buffer;
};
#pragma once
#include <iostream>
#include <sstream>
// Ära neid muutujaid ja funktsioone redigeeri.
static inline int g_TotalTestCount = 0;
static inline int g_SuccessfulTestCount = 0;
#define TEST(condition) testAssert(condition, __FILE__, __LINE__)
inline void testAssert(bool condition, const char *file, int line) {
g_TotalTestCount++;
if (!condition) {
std::cout << "VIGA: Kontroll asukohas " << file << ":" << line << " ei olnud õige.\n";
} else {
g_SuccessfulTestCount++;
}
}
template<typename T>
auto equal(const T &a, const T &b) {
if (a.size() != b.size()) {
return false;
}
return std::equal(a.begin(), a.end(), b.begin());
}
auto all(const auto &c, const auto val) {
return all(c.begin(), c.end(), val);
}
auto all(const auto *begin, const auto *end, const auto val) {
return std::all_of(begin, end,
[&val](const auto &v) { return v == val; });
}
class CoutCapture {
std::stringstream &m_Buffer;
std::streambuf *m_Prevcout;
public:
explicit CoutCapture(std::stringstream &buf) : m_Buffer(buf) {
m_Prevcout = std::cout.rdbuf(m_Buffer.rdbuf());
}
~CoutCapture() {
std::cout.rdbuf(m_Prevcout);
}
};
\ No newline at end of file
#pragma once
// 1. ülesanne
// Implementeeri erinevad funktsioonimallid
// 1.0. Näide. Funktsioonimall `max`, millel on üks tüübiparameeter ning 2 funktsiooniargumenti.
// Funktsioon tagastab argumentidest suurima.
template<typename T>
T max(T a, T b) {
return a > b ? a : b;
}
// 1.1. Funktsioonimall `swap`, millel on üks tüübiparameeter ning 2 funktsiooniargumenti, mis on viidad (*).
// Funktsioon vahetab antud argumentide väärtused. Funktsioon ise ei tagasta midagi.
template<typename T>
void swap(T *a, T *b) {
T tmp = *a;
*a = *b;
*b = tmp;
}
// 1.2. Funktsioonimall `swap`, millel on üks tüübiparameeter ning 2 funktsiooniargumenti, mis on viited (&).
// Funktsioon vahetab antud argumentide väärtused. Funktsioon ise ei tagasta midagi.
template<typename T>
void swap(T &a, T &b) {
T tmp = a;
a = b;
b = tmp;
}
#include <iostream>
#include "Memory.hpp"
#include "MinuPaar.hpp"
#include "MinuVektor.hpp"
#include "String.hpp"
#include "Tester.hpp"
#include "Utilities.hpp"
// Soovitav on lahendada ülesandeid testide järjekorras
// 100% arvestuse saamiseks on vaja ära lahendada 3 ülesannet
int main() {
// Näidisülesannete testid
// Need ei lähe lõpliku arvestuse testidesse
{
// Vaikekonstruktor
const String s;
TEST(s.size() == 0);
// Operaator `bool operator==(const char *)`
TEST(s == "");
}
{
String s("hello world!");
TEST(s.size() == 12);
TEST(s == "hello world!");
s.append("hello world!!!");
TEST(s.size() == 26);
TEST(s == "hello world!hello world!!!");
}
{
String s;
s.append("hello");
s.append(' ');
s.append("world");
s.append('!');
const auto str = s.str();
TEST(str == "hello world!");
}
{
const String s1("hello");
const String s2("hello");
const String s3("world");
// Operaator `bool operator==(const String &)`
TEST(s1 == s2);
// Pane tähele, et vahel oskab kompilaator ise vastandoperaatori `bool
// operator!=(const String &)` implementeerida
TEST(s1 != s3);
TEST(s2 != s3);
}
{
const MinuPaar<int, int> paar{};
TEST(paar.first == 0);
TEST(paar.second == 0);
const MinuPaar paar2{10, 20};
TEST(paar2.first == 10);
TEST(paar2.second == 20);
}
// Need on siin testide loenduri lähtestamiseks
g_SuccessfulTestCount = 0;
g_TotalTestCount = 0;
// 1. ülesanne. Funktsioonimallid
{
// 1.1. `swap(*, *)`
int a = 1;
int b = 2;
swap(&a, &b);
TEST(a == 2);
TEST(b == 1);
}
{
// 1.2. `swap(&, &)`
int a = 1;
int b = 2;
swap(a, b);
TEST(a == 2);
TEST(b == 1);
}
// 2. ülesanne
{
// Tüübipiirangute kontroll
TEST(testGenerateType<int>());
TEST(testGenerateType<float>());
TEST(!testGenerateType<const char *>());
auto buffer = generate<int>(0);
TEST(buffer == nullptr);
delete[] buffer;
buffer = generate<int>(100);
TEST(buffer != nullptr);
delete[] buffer;
}
// 3. ülesanne. Klassimall `MinuVektor<T>`
{
const MinuVektor<int> v;
TEST(v.size() == 0);
TEST(v.capacity() == 2);
TEST(v.buf() != nullptr);
}
{
const MinuVektor<int> v(10);
TEST(v.size() == 0);
TEST(v.capacity() == 10);
TEST(v.buf() != nullptr);
}
{
const MinuVektor v(10, 1);
TEST(v.size() == 10);
TEST(v.capacity() == 10);
const auto buf = v.buf();
TEST(all(buf, buf + v.size(), 1));
}
{
MinuVektor<int> v(10);
v.push_back(1);
TEST(v.size() == 1);
v.push_back(2);
TEST(v.size() == 2);
MinuVektor<int> v2;
for (int i = 0; i < 100; i++) {
v2.push_back(i);
}
TEST(v2.size() == 100);
}
{
MinuVektor<int> v(10);
auto opt = v.get(0);
TEST(opt == std::nullopt);
v.push_back(1);
opt = v.get(0);
TEST(opt.value() == 1);
opt = v.get(1);
TEST(opt == std::nullopt);
}
{
MinuVektor<int> v(10);
auto opt = v.front();
TEST(opt == std::nullopt);
opt = v.back();
TEST(opt == std::nullopt);
}
{
MinuVektor<int> v(10);
v.push_back(1);
v.push_back(2);
TEST(v[0] == 1);
TEST(v[1] == 2);
}
{
MinuVektor<int> v(10);
for (int i = 0; i < 10; i++) {
v.push_back(i);
}
std::stringstream buf;
if (CoutCapture cap(buf); true) {
std::cout << v;
}
TEST(buf.str() == "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}");
}
// Lisaülesanded
// Testide lubamiseks muuda 0 -> 1
#if 0
{
// LÜ 1
auto v = MinuVektor<int>::iota(10);
bool result = true;
for (size_t i = 0; i < 10; i++) {
auto correct = v[i] == i;
result &= correct;
}
TEST(result);
}
#endif
#if 0
{
// LÜ 2
auto v = MinuVektor<int>::iota(10);
const auto fn = [](int i) { return static_cast<float>(i) + 0.25f; };
auto fv = v.transform<float>(fn);
bool result = true;
for (size_t i = 0; i < 10; i++) {
auto correct = fv[i] == static_cast<float>(i) + 0.25f;
result &= correct;
}
TEST(result);
}
#endif
std::cout << "Testide tulemus: " << g_SuccessfulTestCount << "/"
<< g_TotalTestCount << '\n';
return g_SuccessfulTestCount != g_TotalTestCount;
}
#include "String.hpp"
#include <cassert>
#include <cstring>
// Vaikekonstruktor initsialiseerib lihtsalt puhvri
String::String() : m_Buffer(new char[m_Capacity]) {}
// Konstruktor `const char *` tüübist teeb
// sellest korrektselt sõne ning jätab puhvrisse
// pisut rohkem ruumi, et ei peaks kohe seda suurendama
// hakkama
// NB! Pane tähele liikmemuutujate initsialiseerimise
// järjekorda!
String::String(const char *s)
: m_Size(strlen(s)), m_Capacity(m_Size * 2),
m_Buffer(new char[m_Capacity]) {
strncpy(m_Buffer, s, m_Size);
}
// Destruktor vabastab allokeeritud mälu
// Pane tähele, et on kasutatud `delete[]` ning
// võime alati eeldada, et `m_Buffer` on allokeeritud
// korrektselt. Miks?
String::~String() { delete[] m_Buffer; }
// Liikmefunktsioon `append(const char *)`
// Mõtle iseseisvalt, mida see funktsioon teeb ning
// kas see on korrektne. Kas siin võib olla probleeme?
void String::append(const char *s) {
auto len = strlen(s);
auto newLen = m_Size + len;
if (newLen >= m_Capacity) {
resize(newLen);
}
memcpy(m_Buffer + m_Size, s, len);
m_Size += len;
m_Buffer[m_Size] = '\0';
}
// Liikmefunktsioon `append(char)`
// Suurendame samuti puhvrit hetkel naiivselt 2x
void String::append(char c) {
if (m_Size + 1 >= m_Capacity) {
resize(m_Size * 2);
}
m_Buffer[m_Size] = c;
m_Size += 1;
m_Buffer[m_Size] = '\0';
}
// Liikmefunktsioon `append(const String &)`
// Saame ära kasutada liikmefunktsiooni `data`.
// Kas seda funktsiooni saaks teha optimaalsemaks?
// Näiteks nii, et igal kutsel ei pea funktsioonis
// `append(const char *)` sõne pikkust uuesti arvutama,
// kuna siin tehniliselt teame seda?
void String::append(const String &s) { append(s.data()); }
// Privaatne liikmefunktsioon `resize(size_t)`
// Suurendab puhvrit ning kopeerib eelmised elemendid ümber
void String::resize(size_t newCapacity) {
assert(m_Buffer != nullptr);
// Hetkel ei ole siin mõtet sõne väiksemaks teha, seega
// selleks siin see assert.
assert(newCapacity > m_Capacity);
// Mõtle hoolega, mida see koodilõik siin teeb
auto newBuffer = new char[newCapacity];
memcpy(newBuffer, m_Buffer, m_Size);
delete[] m_Buffer;
m_Buffer = newBuffer;
m_Capacity = newCapacity;
}
// Liikmefunktsioon `std::string str() const`
// Konstrueerime std::string objekti
// Pane tähele, et see funktsioon on `const`
std::string String::str() const { return std::string(data()); }
// Võrdlusoperaator `bool operator==(const String &other)`
// Pane tähele, et argument saab olla `const` kuna liikmefunktsioon
// `const char *data() const` on `const`, mistõttu saame seda `const`
// objektil kasutada. Lisaks saame välja kutsuda teise signatuuriga
// operaatori.
bool String::operator==(const String &other) const {
return operator==(other.data());
}
bool String::operator==(const char *other) const {
return strcmp(data(), other) == 0;
}
#pragma once
#include <vector>
std::vector<int> vLisaCopy(std::vector<int> v, int i) {
v.push_back(i);
return v;
}
std::vector<int> &vLisaRef(std::vector<int> &v, int i) {
v.push_back(i);
return v;
}
std::vector<int> *vLisaPtr(std::vector<int> *v, int i) {
v->push_back(i);
return v;
}
std::vector<int *> iota(const int n) {
std::vector<int *> v(n);
for (int i = 0; i < n; ++i) {
v[i] = new int(i + 1);
}
return v;
}
void destroy(std::vector<int *> &v) {
for (auto &ptr : v) {
delete ptr;
ptr = nullptr;
}
}
\ No newline at end of file
#pragma once
#include <cassert>
#include <optional>
template<typename T>
class MinuVektor {
public:
MinuVektor() {
m_Buffer = new T[m_Capacity];
}
explicit MinuVektor(size_t capacity)
: m_Capacity(capacity) {
m_Buffer = new T[capacity];
}
MinuVektor(size_t capacity, T initial)
: m_Size(capacity), m_Capacity(capacity) {
m_Buffer = new T[capacity];
for (size_t i = 0; i < m_Capacity; i++) {
m_Buffer[i] = initial;
}
}
~MinuVektor() {
delete[] m_Buffer;
}
void push_back(T value) {
if (m_Size >= m_Capacity) {
resize(m_Capacity * 2);
}
m_Buffer[m_Size++] = value;
}
std::optional<T> get(size_t idx) {
if (idx >= m_Size) {
return std::nullopt;
}
return m_Buffer[idx];
}
std::optional<T> front() {
return get(0);
}
std::optional<T> back() {
return get(m_Size - 1);
}
T operator[](size_t idx) {
assert(idx < m_Size);
return *get(idx);
}
template<typename U = T>
requires std::integral<U>
static MinuVektor<U> iota(size_t sz) {
MinuVektor v(sz);
for (size_t i = 0; i < sz; i++) {
v.push_back(i);
}
return v;
}
template<typename U>
MinuVektor<U> transform(auto fn) const {
MinuVektor<U> v(size());
for (auto i = 0u; i < size(); i++) {
v.push_back(fn(m_Buffer[i]));
}
return v;
}
[[nodiscard]] const T *buf() const { return m_Buffer; }
[[nodiscard]] auto size() const { return m_Size; }
[[nodiscard]] auto capacity() const { return m_Capacity; }
private:
void resize(const size_t newCapacity) {
if (newCapacity < m_Capacity) {
return;
}
T *newBuffer = new T[newCapacity];
for (size_t i = 0; i < m_Capacity; i++) {
newBuffer[i] = m_Buffer[i];
}
delete[] m_Buffer;
m_Buffer = newBuffer;
m_Capacity = newCapacity;
}
private:
size_t m_Size{0};
size_t m_Capacity{2};
T *m_Buffer = nullptr;
};
template<typename T>
std::ostream &operator<<(std::ostream &os, MinuVektor<T> &v) {
os << '{';
for (size_t i = 0; i < v.size(); i++) {
os << v[i];
if (i != v.size() - 1) {
os << ", ";
}
}
os << '}';
return os;
}
\ No newline at end of file
#pragma once
#include <cstdlib>
#include <vector>
// Ülesanne 1. Funktsiooniobjektid ("funktor").
// Kirjuta klass `Sequence`, mis modelleerib sisult jada, mis hoiab sealt elementide võtmisel järge ning selle klassi
// objekti "kutsumisel" tagastatakse järjekorras järgmine element.
// Klass peab sisemiselt andmeid hoidma kuhimälus oleval mälul. Klass ei tohi kasutada STL-is olemasolevat konteinerit,
// vaid peab ise dünaamiliselt mälu haldama.
// Võid eeldada, et jadasse elemente peale selle konstrueerimist juurde ei lisata.
// Vihjeks: on vajalik vaid üks `new` ja `delete`. Puhvrit suuremaks ega väiksemaks tegema ei pea.
//
// Klass peab käituma järgmiselt:
//
// Klassil on 2 konstruktorit:
// - `Sequence(const std::vector<int> &values)` - konstrueerib jada ning kopeerib kõik väärtused kuhimällu.
// - `Sequence(const std::initializer_list<int> list)` - konstrueerib jada ning kopeerib initsialiseerimisnimekirja
// elemendid kuhimällu.
// Klass ei tohi olla vaikimisi konstrueeritav! S.t, et `Sequence s;` ei tohi töötada (näites olemas).
// Klassi kustutamisel peab destruktor küsitud mälu vabastama.
//
// Klass peab olema "kutsutav" (funktsiooniobjekti omadustega), s.t, et sellele on "kutseoperaator".
//
// Klassil peavad olema järgmised kutseoperaatorid
// - Ilma argumendita operaator, mis tagastab jada järjekorras järgmise elemendi ning liigutab "järge" edasi.
// - Operaator ühe argumendiga tüüpi `size_t skip`, mis jätab vahele `skip` elementi ning tagastab vastava elemendi.
// Samuti liigutab "järge" edasi vastavalt vahele jäetud elementide arvust.
//
// Näide:
// Sequence s = {1, 2, 3, 4, 5};
// const auto x = s(); // x = 1
// // s() tagastaks hetkel 2
// const auto y = s(1); // y = 3
// const auto z = s(); // z = 4
// const auto w = s(1); // peaks krahhima programmi
//
// - Operaator, mille abil on võimalik klassiobjekt teisendada tõeväärtuseks (`bool`).
// Näide:
// const Sequence s = {1};
// const bool b = static_cast<bool>(s); // b = true
// // const bool b2 = s; // pane tähele, et operaator peab olema `explicit`, et see rida ei töötaks.
//
// Klassil on ka järgmised liikmefunktsioonid
// NB! Pane tähele, et liikmefunktsioonid, mis klassi sisu ei muuda, peavad olema märgitud võtmesõnaga `const`.
// Lisaks oleks hea, kui funktsioonid, mis tagastavad midagi, võiksid olla märgitud atribuudiga `[[nodiscard]]`.
//
// - `int *end()` - tagastab viimasest elemendist järgmise. Tegelikult on see allokeeritud puhvrist väljas, aga hea
// markeering selleks, et oleme jõudnud puhvri lõppu. Kõik STL-i konteinerid teevad seda sarnaselt (vt std::vector::end()).
// - `size_t available()` - tagastab alles olevate elementide arvu.
// - `std::vector<int> vec()` - tagastab alles olevad elemendid uues vektoris ning ei tohi jada järge edasi liigutada.
//
// Klassil on järgmised staatilised funktsioonid:
// - `Sequence seq(const Sequence &s)` - mis konstrueerib ja tagastab jadas `s` alles jäänud elementidest uue jada.
// Vihje: Antud funktsioon on lahenduv 2 reaga ;)
//
// Soovitus: Alusta lahendamist all antud mallist.
//
// NB! Pane tähele, et ilmutatud ("explicit") teste automaatselt ei tehta. Need vaatan käsitsi.
class Sequence {
public:
Sequence() = delete;
explicit Sequence(const std::vector<int> &values);
Sequence(std::initializer_list<int> list);
// !!! Ära neid 2 modifitseeri
Sequence(const Sequence &) = delete;
Sequence &operator=(const Sequence &) = delete;
// !!!
~Sequence();
static Sequence sec(const Sequence &s);
[[nodiscard]] auto end() const;
[[nodiscard]] auto available() const -> size_t;
int operator()();
int operator()(size_t skip);
explicit operator bool() const;
[[nodiscard]] std::vector<int> vec() const;
private:
int *m_Head;
size_t m_Size;
int *m_Ptr{};
};
#pragma once
#include <print>
#include <functional>
#include <iostream>
#include <sstream>
// Ära neid muutujaid ja funktsioone redigeeri.
static inline int g_TotalTestCount = 0;
static inline int g_SuccessfulTestCount = 0;
#define TEST(condition) testAssert(condition, __FILE__, __LINE__)
inline void testAssert(bool condition, const char *file, int line) {
g_TotalTestCount++;
if (!condition) {
std::cout << "VIGA: Kontroll asukohas " << file << ":" << line << " ei olnud õige.\n";
} else {
g_SuccessfulTestCount++;
}
}
template<typename T>
auto equal(const T &a, const T &b) {
if (a.size() != b.size()) {
return false;
}
return std::equal(a.begin(), a.end(), b.begin());
}
auto all(const auto begin, const auto end, const auto val) {
const auto fn = [&val](const auto v) -> bool { return v == val; };
return std::all_of(begin, end, fn);
}
auto all(const auto &c, const auto val) {
return all(std::begin(c), std::end(c), val);
}
class CoutCapture {
std::stringstream &m_Buffer;
std::streambuf *m_Prevcout;
public:
explicit CoutCapture(std::stringstream &buf) : m_Buffer(buf) {
m_Prevcout = std::cout.rdbuf(m_Buffer.rdbuf());
}
~CoutCapture() {
std::cout.rdbuf(m_Prevcout);
}
explicit operator bool() const {
return m_Prevcout != std::cout.rdbuf();
}
static bool expect(const std::string &str, auto fn) {
std::stringstream buf;
if (const CoutCapture cap(buf); cap) {
if constexpr (std::is_same_v<decltype(fn()), bool>) {
auto res = fn();
if (!res) {
return false;
}
} else {
fn();
}
}
return buf.str() == str;
}
};
\ No newline at end of file
#include <algorithm>
#include <iostream>
#include <map>
#include <numeric>
#include <ranges>
#include "MinuVektor.hpp"
#include "Funktsioonid.hpp"
#include "Sequence.hpp"
#include "Tester.hpp"
int main() {
// Ülesanne 1. Sequence
{
// Konstruktor `Sequence(const std::vector<int> &values)`
const std::vector v = {1, 2, 3};
const Sequence s(v);
TEST(s.available() == v.size());
}
{
// Konstruktor `Sequence(const std::initializer_list<int> list)`
const Sequence s = {1, 2, 3, 4};
TEST(s.available() == 4);
}
{
// operator()
Sequence s = {1, 2, 3, 4};
std::vector<int> v(s.available());
for (size_t i = 0; i < 4; ++i) {
v[i] = s();
}
TEST(v.size() == 4);
TEST(equal(v, {1, 2, 3, 4}));
TEST(s.available() == 0);
// s(); // - see peaks programmi krahhima
}
{
// Konstruktor `Sequence(const Sequence &s)`
Sequence s = {1, 2, 3};
s();
const auto s2 = Sequence::sec(s);
TEST(equal(s2.vec(), {2, 3}));
}
{
// operator(size_t)
Sequence s = {1, 2, 3, 4, 5};
s();
TEST(s(1) == 3);
TEST(s.available() > 0);
TEST(s() == 4);
TEST(s() == 5);
TEST(s.available() == 0);
// s(); // - see peaks programmi krahhima
}
{
[[maybe_unused]] Sequence s = {1};
// s(1); // - see peaks programmi krahhima
}
{
// operator bool
Sequence s = {1};
TEST(static_cast<bool>(s));
s();
TEST(!static_cast<bool>(s));
}
{
// vec
const Sequence s = {1, 2};
const auto vec = s.vec();
TEST(equal(vec, {1, 2}));
Sequence s2 = {1, 2};
TEST(s2(1) == 2);
const auto vec2 = s2.vec();
TEST(vec2.empty());
}
{
Sequence s = {1, 2, 3, 4, 5, 6};
int current = 1;
bool result = true;
// Siin on operator bool kasulik:
while (s && result) {
const auto correct = (current++) == s();
result &= correct;
}
TEST(result);
}
// Ülesanne 2. Funktsioonid
// Kirjuta lahendus korrektselt päise- ja lähtekoodifailidesse nii, et
// allolevad testid läbiksid Vaata, et lisaksid ka lähtekoodifaili faili
// CMakeLists.txt ning impordiksid päisefaili õigesti NB! Pane tähele, et
// funktsioonid ei ole mallid, s.t peavad olema jaotatud nii päise- kui ka
// lähtekoodifailide vahel.
// Antud ülesande lahendamisel ignoreeri CLion'i antud hoiatusi koopiate ja
// reference'ide kohta.
{
std::vector v = {1, 2, 3};
const std::vector v2 = vLisaCopy(v, 4);
TEST(equal(v2, {1, 2, 3, 4}));
TEST(equal(v, {1, 2, 3}));
}
{
std::vector v = {1, 2, 3};
const auto &v2 = vLisaRef(v, 4);
// Funktsioon peab tagastama sama vektori, mis talle anti
TEST(&v == &v2);
TEST(equal(v, {1, 2, 3, 4}));
TEST(equal(v2, {1, 2, 3, 4}));
}
{
std::vector v = {1, 2, 3};
const auto *v2 = vLisaPtr(&v, 4);
// Funktsioon peab tagastama sama vektori, mis talle anti
TEST(&v == v2);
TEST(equal(v, {1, 2, 3, 4}));
// Mõtle ja katseta, kas allolev rida teeb vektorist koopia?
const auto &v2Ref = *v2;
TEST(equal(v2Ref, {1, 2, 3, 4}));
}
// Ülesanne 3. Vektor viitadest
// Kirjuta funktsioon `std::vector<int *> iota(int n)`, mis tagastab vektori,
// kus on kuhimälus allokeeritud täisarvud, mis sisaldavad väärtuseid [0, ...,
// n]. Võid eeldada, et `n > 0`. Kirjuta funktsioon `destroy(std::vector<int
// *> &v)`, mis vabastab kõik täisarvud ning seab vektori viidad võrduma
// väärtusega `nullptr`. Vasta ka küsimusele, miks argument peab olema viite
// tüüpi ning miks ei tohi vektorit ennast "kustutada".
{
const int N = 10;
auto vec = iota(N);
bool result = true;
for (int i = 0; i < N; ++i) {
const auto ptr = vec[i];
const auto correct = *ptr == (i + 1);
result &= correct;
}
TEST(result);
destroy(vec);
TEST(all(vec, nullptr));
}
std::cout << "Testide tulemus: " << g_SuccessfulTestCount << "/"
<< g_TotalTestCount << '\n';
return g_SuccessfulTestCount != g_TotalTestCount;
}
#include "Sequence.hpp"
#include <cassert>
Sequence::Sequence(const std::vector<int> &values)
: m_Size(values.size()) {
m_Head = new int[m_Size];
m_Ptr = m_Head;
for (size_t i = 0; i < m_Size; ++i) {
m_Ptr[i] = values[i];
}
}
Sequence::Sequence(const std::initializer_list<int> list)
: m_Size(list.size()) {
m_Head = new int[m_Size];
m_Ptr = m_Head;
auto ptr = list.begin();
for (size_t i = 0; i < m_Size; ++i, ++ptr) {
m_Ptr[i] = *ptr;
}
}
Sequence::~Sequence() {
delete []m_Head;
}
auto Sequence::end() const {
return m_Head + m_Size;
}
auto Sequence::available() const -> size_t {
return end() - m_Ptr;
}
int Sequence::operator()() {
assert(available());
const auto value = *m_Ptr;
m_Ptr++;
return value;
}
int Sequence::operator()(const size_t skip) {
assert(skip < available());
for (size_t i = 0; i < skip; ++i) {
m_Ptr++;
}
const auto value = *m_Ptr;
m_Ptr++;
return value;
}
Sequence::operator bool() const {
return available() != 0;
}
std::vector<int> Sequence::vec() const {
const auto size = available();
std::vector<int> v(size);
for (size_t i = 0; i < size; ++i) {
v[i] = m_Ptr[i];
}
return v;
}
Sequence Sequence::sec(const Sequence &s) {
const auto vec = s.vec();
return Sequence(vec);
}