1 // Copyright 2015, Tobias Hermann and the FunctionalPlus contributors. 2 // https://github.com/Dobiasd/FunctionalPlus 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 8 // tests for exercise solutions of the Udemy course 9 // "Functional Programming using C++" 10 // https://www.udemy.com/functional-programming-using-cpp/ 11 12 #include <doctest/doctest.h> 13 #include <fplus/fplus.hpp> 14 #include <vector> 15 16 namespace Correctness_follows_from_expressiveness 17 { is_even(int x)18 bool is_even(int x) 19 { 20 return x % 2 == 0; 21 } 22 23 template <typename Pred, typename Cont> keep_if(Pred pred,const Cont & xs)24 Cont keep_if(Pred pred, const Cont& xs) 25 { 26 Cont ys; 27 for (const auto x : xs) 28 { 29 if (pred(x)) 30 { 31 ys.push_back(x); 32 } 33 } 34 return ys; 35 } 36 } 37 38 TEST_CASE("udemy_course_test - Correctness_follows_from_expressiveness") 39 { 40 using namespace Correctness_follows_from_expressiveness; 41 std::vector<int> xs = {0,1,2,3,4}; 42 const auto ys = keep_if(is_even, xs); 43 REQUIRE_EQ(ys, std::vector<int>({0,2,4})); 44 } 45 46 namespace Programming_challenge_parse_and_product 47 { str_to_double(const std::string & str)48 double str_to_double(const std::string& str) 49 { 50 double result; 51 std::istringstream(str) >> result; 52 return result; 53 } 54 } 55 56 TEST_CASE("udemy_course_test - Programming_challenge_parse_and_product") 57 { 58 using namespace Programming_challenge_parse_and_product; 59 const std::string input = "1,5,4,7,2,2,3.34"; 60 const auto parts = fplus::split(',', false, input); 61 62 const auto nums = 63 fplus::transform(str_to_double, parts); 64 65 const auto result = 66 fplus::reduce(std::plus<double>(), 1, nums); 67 68 REQUIRE(fplus::is_in_interval_around(0.001, 25.34, result)); 69 } 70 71 namespace Programming_challenge_longest_edge_of_polygon 72 { 73 typedef std::pair<float, float> point; 74 point_distance(const point & p1,const point & p2)75 float point_distance(const point& p1, const point& p2) 76 { 77 const float dx = p2.first - p1.first; 78 const float dy = p2.second - p1.second; 79 return std::sqrt(dx * dx + dy * dy); 80 } 81 } 82 83 TEST_CASE("udemy_course_test - Programming_challenge_longest_edge_of_polygon") 84 { 85 using namespace std; 86 using namespace Programming_challenge_longest_edge_of_polygon; 87 88 vector<point> polygon = 89 { {1.f,2.f}, {7.f,3.f}, {6.f,5.f}, {4.f,4.f}, {2.f,9.f} }; 90 91 const auto edges = 92 fplus::overlapping_pairs_cyclic(polygon); 93 94 const auto result = fplus::maximum_on( 95 [](const std::pair<point, point>& edge) -> float __anon3b408f290102(const std::pair<point, point>& edge) 96 { 97 return point_distance(edge.first, edge.second); 98 }, edges); 99 100 REQUIRE_EQ(fplus::show(result), std::string("((2, 9), (1, 2))")); 101 } 102 103 namespace The_problem_with_comments 104 { str_to_int(const std::string & str)105 int str_to_int(const std::string& str) 106 { 107 int result; 108 std::istringstream(str) >> result; 109 return result; 110 } 111 112 template <typename Cont> product(const Cont & xs)113 typename Cont::value_type product(const Cont& xs) 114 { 115 return fplus::reduce(std::multiplies<int>(), 1, xs); 116 } 117 118 template <typename Cont> sum(const Cont & xs)119 typename Cont::value_type sum(const Cont& xs) 120 { 121 return fplus::reduce(std::plus<int>(), 1, xs); 122 } 123 } 124 125 TEST_CASE("udemy_course_test - The_problem_with_comments") 126 { 127 using namespace The_problem_with_comments; 128 const std::string input = "1,5,4,7,2,2,3"; 129 const auto parts = fplus::split(',', false, input); 130 const auto nums = fplus::transform(str_to_int, parts); 131 const auto result = product(nums); // sum(nums) 132 REQUIRE_EQ(result, 1680); 133 } 134 135 namespace High_level_expressiveness_and_concise_code 136 { 137 typedef std::pair<float, float> point; 138 typedef std::pair<point, point> edge; 139 typedef std::vector<point> points; 140 point_distance(const point & p1,const point & p2)141 float point_distance(const point& p1, const point& p2) 142 { 143 const float dx = p2.first - p1.first; 144 const float dy = p2.second - p1.second; 145 return std::sqrt(dx * dx + dy * dy); 146 } 147 edge_length(const edge & e)148 float edge_length(const edge& e) 149 { 150 return point_distance(e.first, e.second); 151 } 152 get_edges(const points & polygon)153 std::vector<edge> get_edges(const points& polygon) 154 { 155 return fplus::overlapping_pairs_cyclic(polygon); 156 } 157 } 158 159 TEST_CASE("udemy_course_test - High_level_expressiveness_and_concise_code") 160 { 161 using namespace std; 162 using namespace High_level_expressiveness_and_concise_code; 163 164 vector<point> polygon = 165 { {1.f,2.f}, {7.f,3.f}, {6.f,5.f}, {4.f,4.f}, {2.f,9.f} }; 166 167 const auto result = fplus::maximum_on( 168 edge_length, 169 get_edges(polygon)); 170 171 REQUIRE_EQ(fplus::show(result), std::string("((2, 9), (1, 2))")); 172 } 173 174 namespace Currying_and_partial_function_application 175 { 176 } 177 178 TEST_CASE("udemy_course_test - Currying_and_partial_function_application") 179 { 180 using namespace Currying_and_partial_function_application; 181 182 std::vector<std::vector<int>> xss = 183 {{0,1,2}, {3,4,5}}; 184 185 // 1 186 fplus::transform(fplus::fwd::transform(fplus::square<int>), xss); 187 188 // 2 189 const auto add_four_curried = [](int a) __anon3b408f290202(int a) 190 { 191 return [a](int b) 192 { 193 return [a, b](int c) 194 { 195 return [a, b, c](int d) 196 { 197 return a + b + c + d; 198 }; 199 }; 200 }; 201 }; 202 203 REQUIRE_EQ(add_four_curried(1)(2)(3)(4), 1+2+3+4); 204 } 205 206 namespace Forward_application 207 { 208 typedef std::pair<float, float> point; 209 typedef std::pair<point, point> edge; 210 typedef std::vector<point> points; 211 point_distance(const point & p1,const point & p2)212 float point_distance(const point& p1, const point& p2) 213 { 214 const float dx = p2.first - p1.first; 215 const float dy = p2.second - p1.second; 216 return std::sqrt(dx * dx + dy * dy); 217 } 218 edge_length(const edge & e)219 float edge_length(const edge& e) 220 { 221 return point_distance(e.first, e.second); 222 } 223 get_edges(const points & polygon)224 std::vector<edge> get_edges(const points& polygon) 225 { 226 return fplus::overlapping_pairs_cyclic(polygon); 227 } 228 } 229 230 TEST_CASE("udemy_course_test - Forward_application") 231 { 232 using namespace std; 233 using namespace Forward_application; 234 235 vector<point> polygon = 236 { {1.f,2.f}, {7.f,3.f}, {6.f,5.f}, {4.f,4.f}, {2.f,9.f} }; 237 238 // 1: 239 const auto result = fplus::fwd::apply(polygon 240 , get_edges 241 , fplus::fwd::maximum_on(edge_length)); 242 243 REQUIRE_EQ(fplus::show(result), std::string("((2, 9), (1, 2))")); 244 245 // 2: 246 int a = 3; 247 248 // intermediate values 249 int b = fplus::square(a); 250 int c = fplus::min_2(2, b); 251 int d = fplus::abs_diff(7, c); 252 int e = fplus::clamp(1, 4, d); 253 int f = fplus::max_2(6, e); 254 REQUIRE_EQ(f, 6); 255 256 // nested function calls 257 int f_nested = fplus::max_2(6, 258 fplus::clamp(1, 4, 259 fplus::abs_diff(7, 260 fplus::min_2(2, 261 fplus::square(a))))); 262 REQUIRE_EQ(f_nested, 6); 263 264 // foward-application style 265 int f_fwd = fplus::fwd::apply(a 266 , fplus::fwd::square() 267 , fplus::fwd::min_2(2) 268 , fplus::fwd::abs_diff(7) 269 , fplus::fwd::clamp(1, 4) 270 , fplus::fwd::max_2(6)); 271 REQUIRE_EQ(f_fwd, 6); 272 } 273 274 namespace Programming_challenge_Interacting_with_the_command_line 275 { 276 // cmd_line_interact : (String -> String) -> () 277 template <typename F> cmd_line_interact(F)278 void cmd_line_interact(F) 279 { 280 // no side effects in unit tests 281 } 282 } 283 284 TEST_CASE("udemy_course_test - Programming_challenge_Interacting_with_the_command_line") 285 { 286 using namespace fplus; 287 using namespace Programming_challenge_Interacting_with_the_command_line; 288 289 // 1: 290 cmd_line_interact(fwd::to_upper_case()); 291 292 // 2: 293 cmd_line_interact( 294 fwd::compose( 295 fwd::split_lines(false), 296 fwd::sort(), 297 fwd::join(std::string("\n")))); 298 } 299 300 namespace Function_composition 301 { str_to_double(const std::string & str)302 double str_to_double(const std::string& str) 303 { 304 double result; 305 std::istringstream(str) >> result; 306 return result; 307 } 308 309 const auto parse_and_product = fplus::fwd::compose( 310 fplus::fwd::split(',', false), 311 fplus::fwd::transform(str_to_double), 312 fplus::fwd::product()); 313 } 314 315 TEST_CASE("udemy_course_test - Function_composition") 316 { 317 using namespace Function_composition; 318 const std::string input = "1,5,4,7,2,2,3.34"; 319 const auto result = parse_and_product(input); 320 REQUIRE(fplus::is_in_interval_around(0.001, 1870.4, result)); 321 } 322 323 namespace Programming_challenge_an_SQL_analogy 324 { 325 struct user 326 { 327 std::string name; 328 std::string country; 329 std::size_t visits; 330 }; 331 get_country(const user & u)332 std::string get_country(const user& u) 333 { 334 return u.country; 335 } 336 get_visits(const user & u)337 std::size_t get_visits(const user& u) 338 { 339 return u.visits; 340 } 341 } 342 343 TEST_CASE("udemy_course_test - Programming_challenge_an_SQL_analogy") 344 { 345 using namespace Programming_challenge_an_SQL_analogy; 346 347 const std::vector<user> users = { 348 {"Nicole", "GER", 2}, 349 {"Justin", "USA", 1}, 350 {"Rachel", "USA", 5}, 351 {"Robert", "USA", 6}, 352 {"Stefan", "GER", 4} 353 }; 354 355 356 const auto visit_sum = [](const std::vector<user>& xs) -> std::size_t __anon3b408f290602(const std::vector<user>& xs) 357 { 358 return fplus::fwd::apply(xs 359 , fplus::fwd::transform(get_visits) 360 , fplus::fwd::sum()); 361 }; 362 363 // n^2 364 const auto result = fplus::fwd::apply(users 365 , fplus::fwd::group_globally_on_labeled(get_country) 366 , fplus::fwd::transform(fplus::fwd::transform_snd(visit_sum)) 367 ); 368 REQUIRE_EQ(fplus::show_cont(result), 369 std::string("[(GER, 6), (USA, 12)]")); 370 371 // n * log(n) 372 const auto result_n_log_n = fplus::fwd::apply(users 373 , fplus::fwd::sort_on(get_country) 374 , fplus::fwd::group_on_labeled(get_country) 375 , fplus::fwd::transform(fplus::fwd::transform_snd(visit_sum)) 376 ); 377 REQUIRE_EQ(fplus::show_cont(result_n_log_n), 378 std::string("[(GER, 6), (USA, 12)]")); 379 } 380 381 namespace Functors 382 { 383 template <typename ValOut, typename F, typename Key, typename ValIn> lift_dict(F f,const std::map<Key,ValIn> & dict)384 std::map<Key, ValOut> lift_dict(F f, const std::map<Key, ValIn>& dict) 385 { 386 std::map<Key, ValOut> result; 387 for (const auto& key_and_val : dict) 388 { 389 result[key_and_val.first] = f(key_and_val.second); 390 } 391 return result; 392 } 393 } 394 395 TEST_CASE("udemy_course_test - Functors") 396 { 397 using namespace Functors; 398 using namespace fplus; 399 std::map<int, double> dict = 400 {{2, 1.41}, {3, 1.73}, {4, 2.0}}; 401 auto dict_squared = lift_dict<double>(square<double>, dict); 402 auto dict_shown = lift_dict<std::string>(show<double>, dict); 403 404 REQUIRE_EQ(show_cont(dict_squared), "[(2, 1.9881), (3, 2.9929), (4, 4)]"); 405 REQUIRE_EQ(show_cont(dict_shown), "[(2, 1.41), (3, 1.73), (4, 2)]"); 406 } 407 408 namespace Monads 409 { 410 using namespace std; 411 using namespace fplus; 412 typedef vector<int> Ints; 413 typedef vector<string> Strings; 414 get_input_filepath(const Strings & args)415 result<string, string> get_input_filepath(const Strings& args) 416 { 417 assert(args.size() > 0); 418 if (args.size() != 2) 419 return error<string, string>( 420 "Usage: " + args[0] + " FILEPATH"); 421 else 422 return ok<string, string>(args[1]); 423 } 424 read_file(const string &)425 result<string, string> read_file(const string&) 426 { 427 // dummy, no side effects in tests 428 return ok<string, string>("1,1,1,4"); 429 } 430 parse_content(const string & content)431 result<Ints, string> parse_content(const string& content) 432 { 433 const auto maybe_values = fwd::apply(content 434 , fwd::split(',', false) 435 , fwd::transform(read_value<int>)); 436 if (all_by(is_just<int>, maybe_values)) 437 return ok<Ints, string>(justs(maybe_values)); 438 else 439 return error<Ints, string>("Can not parse file."); 440 } 441 calc_median(const vector<int> & xs)442 result<int, string> calc_median(const vector<int>& xs) 443 { 444 if (is_not_empty(xs)) 445 return ok<int, string>(median(xs)); 446 return error<int, string>("Need at least one value."); 447 } 448 show_median(int value)449 string show_median(int value) 450 { 451 return "The median is " + show(value); 452 } 453 show_error(const string & error)454 string show_error(const string& error) 455 { 456 return "ERROR: " + error; 457 } 458 459 template <typename Res, typename Error, 460 typename F, typename A> my_and_then_result(F f,const result<A,Error> & r)461 result<Res, Error> my_and_then_result( 462 F f, const result<A, Error>& r) 463 { 464 if (is_ok(r)) 465 return f(unsafe_get_ok(r)); 466 else 467 return error<Res, Error>(r.unsafe_get_error()); 468 } 469 } 470 471 TEST_CASE("udemy_course_test - Monads") 472 { 473 using namespace fplus; 474 using namespace std; 475 using namespace Monads; 476 const Strings arguments = {"executable", "input.txt"}; 477 478 const string error_msg = "An error occured."; 479 480 const auto input_filepath = get_input_filepath(arguments); 481 482 const auto file_content = 483 my_and_then_result<string>(read_file, input_filepath); 484 485 const auto values = 486 my_and_then_result<Ints>(parse_content, file_content); 487 488 const auto res = 489 my_and_then_result<int>(calc_median, values); 490 491 const auto output = unify_result(show_median, show_error, res); 492 493 REQUIRE_EQ(output, std::string("The median is 1")); 494 } 495 496 namespace Multithreading 497 { 498 struct Image {}; // dummy 499 struct FaceImage {}; // dummy 500 std::vector<Image> images; extract_face(Image)501 FaceImage extract_face(Image) { return {}; } // dummy 502 FaceImage empty_face_image; // dummy add_face_images(FaceImage,FaceImage)503 FaceImage add_face_images(FaceImage, FaceImage) { return {}; } // dummy divide_values(FaceImage,std::size_t)504 FaceImage divide_values(FaceImage, std::size_t) { return {}; } // dummy 505 } 506 507 TEST_CASE("udemy_course_test - Multithreading") 508 { 509 using namespace Multithreading; 510 fplus::transform_reduce_parallelly( 511 extract_face, add_face_images, empty_face_image, images); 512 } 513 514 namespace OOP_Design_patterns_vanishing 515 { square(int x)516 int square(int x) 517 { 518 return x * x; 519 } 520 521 // decorate_with_logging : (String, (Int -> Int)) -> (Int -> Int) 522 template <typename F> decorate_with_logging(const std::string & str,F f)523 std::function<int(int)> decorate_with_logging(const std::string& str, F f) 524 { 525 return [str, f](int x) -> int 526 { 527 int result = f(x); 528 // no side effects in tests 529 //std::cout << str << ": " << x << " => " << result << std::endl; 530 return result; 531 }; 532 } 533 } 534 535 TEST_CASE("udemy_course_test - OOP_Design_patterns_vanishing") 536 { 537 using namespace OOP_Design_patterns_vanishing; 538 const auto logging_square = 539 decorate_with_logging("Square", square); 540 541 int a = logging_square(4); 542 int b = logging_square(5); 543 544 REQUIRE_EQ(a, 16); 545 REQUIRE_EQ(b, 25); 546 }