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 }