From 32186152f7fb582360e33a1745c8e2018d5ca497 Mon Sep 17 00:00:00 2001 From: juhlig Date: Mon, 6 Aug 2018 10:31:29 +0200 Subject: [PATCH] the palindrome-products exercise --- config.json | 12 +++ exercises/palindrome-products/HINTS.md | 21 +++++ exercises/palindrome-products/README.md | 76 ++++++++++++++++ exercises/palindrome-products/rebar.config | 30 +++++++ exercises/palindrome-products/src/example.erl | 90 +++++++++++++++++++ .../src/palindrome_products.app.src | 9 ++ .../src/palindrome_products.erl | 11 +++ .../test/palindrome_products_tests.erl | 63 +++++++++++++ 8 files changed, 312 insertions(+) create mode 100644 exercises/palindrome-products/HINTS.md create mode 100644 exercises/palindrome-products/README.md create mode 100644 exercises/palindrome-products/rebar.config create mode 100644 exercises/palindrome-products/src/example.erl create mode 100644 exercises/palindrome-products/src/palindrome_products.app.src create mode 100644 exercises/palindrome-products/src/palindrome_products.erl create mode 100644 exercises/palindrome-products/test/palindrome_products_tests.erl diff --git a/config.json b/config.json index f06687cc..95823956 100644 --- a/config.json +++ b/config.json @@ -577,6 +577,18 @@ "strings" ] }, + { + "slug": "palindrome-products", + "uuid": "bc74b87d-d257-41e5-9233-10324a78367c", + "core": false, + "unlocked_by": "accumulate", + "difficulty": 6, + "topics": [ + "algorithms", + "math", + "performance" + ] + }, { "slug": "circular-buffer", "uuid": "058b48a9-03ec-43bc-88eb-9dbce8b79e59", diff --git a/exercises/palindrome-products/HINTS.md b/exercises/palindrome-products/HINTS.md new file mode 100644 index 00000000..5ee221a6 --- /dev/null +++ b/exercises/palindrome-products/HINTS.md @@ -0,0 +1,21 @@ +A goal of this exercise is teaching you about efficiency. There are many ways +to create a solution that produces correct results, but some solutions are more +efficient and thereby finish faster than others. + +On the `Erlang` track, some of your tests may even time out if your solution is +not efficient enough. + +Here are some general points to consider when it comes to optimize your algorithms: +* Some operations are cheap, others are expensive. Try to figure out which are which, +then try to find a way to perform expensive operations rarely, by executing them +only if needed, eg when a cheap operation alone is not enough. +* Skip out early. Often it is clear that, beyond a certain point, no better solution +can be found, so it makes no sense to search further. +* More generally, reduce the overall steps or iterations your algorithm has to perform. +* When generating results, it may be better to allow duplicates and weed them out +before returning the final result, instead of ensuring that duplicates never get +created in the first place along the way. + +Those are only some general rules of thumb, things you might want to keep an eye out +for. They are not universally applicable to any problem, and often they come with +trade-offs. diff --git a/exercises/palindrome-products/README.md b/exercises/palindrome-products/README.md new file mode 100644 index 00000000..60b326ed --- /dev/null +++ b/exercises/palindrome-products/README.md @@ -0,0 +1,76 @@ +# Palindrome Products + +Detect palindrome products in a given range. + +A palindromic number is a number that remains the same when its digits are +reversed. For example, `121` is a palindromic number but `112` is not. + +Given a range of numbers, find the largest and smallest palindromes which +are products of numbers within that range. + +Your solution should return the largest and smallest palindromes, along with the +factors of each within the range. If the largest or smallest palindrome has more +than one pair of factors within the range, then return all the pairs. + +## Example 1 + +Given the range `[1, 9]` (both inclusive)... + +And given the list of all possible products within this range: +`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]` + +The palindrome products are all single digit numbers (in this case): +`[1, 2, 3, 4, 5, 6, 7, 8, 9]` + +The smallest palindrome product is `1`. Its factors are `(1, 1)`. +The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. + +## Example 2 + +Given the range `[10, 99]` (both inclusive)... + +The smallest palindrome product is `121`. Its factors are `(11, 11)`. +The largest palindrome product is `9009`. Its factors are `(91, 99)`. + +## Running tests + +In order to run the tests, issue the following command from the exercise +directory: + +For running the tests provided, `rebar3` is used as it is the official build and +dependency management tool for erlang now. Please refer to [the tracks installation +instructions](http://exercism.io/languages/erlang/installation) on how to do that. + +In order to run the tests, you can issue the following command from the exercise +directory. + +```bash +$ rebar3 eunit +``` + +### Test versioning + +Each problem defines a macro `TEST_VERSION` in the test file and +verifies that the solution defines and exports a function `test_version` +returning that same value. + +To make tests pass, add the following to your solution: + +```erlang +-export([test_version/0]). + +test_version() -> + 1. +``` + +The benefit of this is that reviewers can see against which test version +an iteration was written if, for example, a previously posted solution +does not solve the current problem or passes current tests. + +## Questions? + +For detailed information about the Erlang track, please refer to the +[help page](http://exercism.io/languages/erlang) on the Exercism site. +This covers the basic information on setting up the development +environment expected by the exercises. + diff --git a/exercises/palindrome-products/rebar.config b/exercises/palindrome-products/rebar.config new file mode 100644 index 00000000..db5d9076 --- /dev/null +++ b/exercises/palindrome-products/rebar.config @@ -0,0 +1,30 @@ +%% Erlang compiler options +{erl_opts, [debug_info, warnings_as_errors]}. + +{deps, [{erl_exercism, "0.1.2"}]}. + +{dialyzer, [ + {warnings, [underspecs, no_return]}, + {get_warnings, true}, + {plt_apps, top_level_deps}, % top_level_deps | all_deps + {plt_extra_apps, []}, + {plt_location, local}, % local | "/my/file/name" + {plt_prefix, "rebar3"}, + {base_plt_apps, [stdlib, kernel, crypto]}, + {base_plt_location, global}, % global | "/my/file/name" + {base_plt_prefix, "rebar3"} +]}. + +%% eunit:test(Tests) +{eunit_tests, []}. +%% Options for eunit:test(Tests, Opts) +{eunit_opts, [verbose]}. + +%% == xref == + +{xref_warnings, true}. + +%% xref checks to run +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions]}. diff --git a/exercises/palindrome-products/src/example.erl b/exercises/palindrome-products/src/example.erl new file mode 100644 index 00000000..da1c5127 --- /dev/null +++ b/exercises/palindrome-products/src/example.erl @@ -0,0 +1,90 @@ +-module(example). + +-export([smallest/2, largest/2, test_version/0]). + + +smallest(Min, Max) when Min>Max -> {error, invalid_range}; + +smallest(Min, Max) -> + case smallest(Min, Max, Min, Min, undefined) of + undefined -> undefined; + {N, Factors} -> {N, dedup_factors(Factors)} + end. + +smallest(_, Max, Cur1, Cur2, Best) when Cur1>Max andalso Cur2>Max -> + Best; + +smallest(Min, Max, Cur1, Cur2, Best) when Cur2>Max -> + smallest(Min, Max, Cur1+1, Cur1+1, Best); + +smallest(Min, Max, Cur1, Cur2, undefined) -> + Candidate=Cur1*Cur2, + case is_palindrome(Candidate) of + false -> smallest(Min, Max, Cur1, Cur2+1, undefined); + true -> smallest(Min, Max, Cur1+1, Cur1+1, {Candidate, [{Cur1, Cur2}]}) + end; + +smallest(Min, Max, Cur1, Cur2, Best={BestProduct, _}) when Cur1*Cur2>BestProduct -> + smallest(Min, Max, Cur1+1, Cur1+1, Best); + +smallest(Min, Max, Cur1, Cur2, {BestProduct, BestFactors}) when Cur1*Cur2=:=BestProduct -> + smallest(Min, Max, Cur1+1, Cur1+1, {BestProduct, [{Cur1, Cur2}|BestFactors]}); + +smallest(Min, Max, Cur1, Cur2, Best) -> + Candidate=Cur1*Cur2, + case is_palindrome(Candidate) of + true -> smallest(Min, Max, Cur1+1, Cur1+1, {Candidate, [{Cur1, Cur2}]}); + false -> smallest(Min, Max, Cur1, Cur2+1, Best) + end. + + +largest(Min, Max) when Min>Max -> {error, invalid_range}; + +largest(Min, Max) -> + case largest(Min, Max, Max, Max, undefined) of + undefined -> undefined; + {N, Factors} -> {N, dedup_factors(Factors)} + end. + +largest(Min, _, Cur1, _, Best) when Cur1 + Best; + +largest(Min, Max, Cur1, Cur2, Best) when Cur2 + largest(Min, Max, Cur1-1, Max, Best); + +largest(Min, Max, Cur1, Cur2, undefined) -> + Candidate=Cur1*Cur2, + case is_palindrome(Candidate) of + false -> largest(Min, Max, Cur1, Cur2-1, undefined); + true -> largest(Min, Max, Cur1-1, Max, {Candidate, [{Cur1, Cur2}]}) + end; + +largest(Min, Max, Cur1, Cur2, Best={BestProduct, _}) when Cur1*Cur2 + largest(Min, Max, Cur1-1, Max, Best); + +largest(Min, Max, Cur1, Cur2, {BestProduct, BestFactors}) when Cur1*Cur2=:=BestProduct -> + largest(Min, Max, Cur1-1, Max, {BestProduct, [{Cur1, Cur2}|BestFactors]}); + +largest(Min, Max, Cur1, Cur2, Best) -> + Candidate=Cur1*Cur2, + case is_palindrome(Candidate) of + true -> largest(Min, Max, Cur1-1, Max, {Candidate, [{Cur1, Cur2}]}); + false -> largest(Min, Max, Cur1, Cur2-1, Best) + end. + + +is_palindrome(Candidate) -> + Candidate2=integer_to_list(Candidate), + Candidate2=:=lists:reverse(Candidate2). + + +dedup_factors(Factors) -> + lists:usort( + fun + ({A1, A2}, {B1, B2}) -> min(A1, A2)= 1. diff --git a/exercises/palindrome-products/src/palindrome_products.app.src b/exercises/palindrome-products/src/palindrome_products.app.src new file mode 100644 index 00000000..494325d4 --- /dev/null +++ b/exercises/palindrome-products/src/palindrome_products.app.src @@ -0,0 +1,9 @@ +{application, palindrome_products, + [{description, "exercism.io - palindrome-products"}, + {vsn, "0.0.1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, + stdlib]}, + {env, []} +]}. diff --git a/exercises/palindrome-products/src/palindrome_products.erl b/exercises/palindrome-products/src/palindrome_products.erl new file mode 100644 index 00000000..7237946d --- /dev/null +++ b/exercises/palindrome-products/src/palindrome_products.erl @@ -0,0 +1,11 @@ +-module(palindrome_products). + +-export([smallest/2, largest/2, test_version/0]). + +smallest(_Min, _Max) -> + undefined. + +largest(_Min, _Max) -> + undefined. + +test_version() -> 1. diff --git a/exercises/palindrome-products/test/palindrome_products_tests.erl b/exercises/palindrome-products/test/palindrome_products_tests.erl new file mode 100644 index 00000000..6bf8ecff --- /dev/null +++ b/exercises/palindrome-products/test/palindrome_products_tests.erl @@ -0,0 +1,63 @@ +%% based on canonical data version 1.1.0 +%% https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/palindrome-products/canonical-data.json + +-module(palindrome_products_tests). + +-define(TEST_VERSION, 1). +-include_lib("erl_exercism/include/exercism.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +smallest_from_single_digit_factors_test() -> + ?assertMatch({1, [{1, 1}]}, normalize_output(palindrome_products:smallest(1, 9))). + +largest_from_single_digit_factors_test() -> + ?assertMatch({9, [{1, 9}, {3, 3}]}, normalize_output(palindrome_products:largest(1, 9))). + +smallest_from_double_digit_factors_test() -> + ?assertMatch({121, [{11, 11}]}, normalize_output(palindrome_products:smallest(10, 99))). + +largest_from_double_digit_factors_test() -> + ?assertMatch({9009, [{91, 99}]}, normalize_output(palindrome_products:largest(10, 99))). + +smallest_from_triple_digit_factors_test() -> + ?assertMatch({10201, [{101, 101}]}, normalize_output(palindrome_products:smallest(100, 999))). + +largest_from_triple_digit_factors_test() -> + ?assertMatch({906609, [{913, 993}]}, normalize_output(palindrome_products:largest(100, 999))). + +smallest_from_four_digit_factors_test() -> + ?assertMatch({1002001, [{1001, 1001}]}, normalize_output(palindrome_products:smallest(1000, 9999))). + +largest_from_four_digit_factors_test() -> + ?assertMatch({99000099, [{9901, 9999}]}, normalize_output(palindrome_products:largest(1000, 9999))). + +smallest_none_in_range_test() -> + ?assertMatch(undefined, palindrome_products:smallest(1002, 1003)). + +largest_none_in_range_test() -> + ?assertMatch(undefined, palindrome_products:largest(15, 15)). + +smallest_min_greater_than_max_test() -> + ?assertMatch({error, invalid_range}, palindrome_products:smallest(10000, 1)). + +largest_min_greater_than_max_test() -> + ?assertMatch({error, invalid_range}, palindrome_products:largest(2, 1)). + +normalize_output({Pal, Factors}) -> + { + Pal, + lists:sort( + lists:map( + fun + ({A, B}) when A>B -> {B, A}; + (AB) -> AB + end, + Factors + ) + ) + }; + +normalize_output(Other) -> Other. + +version_test() -> + ?assertMatch(?TEST_VERSION, palindrome_products:test_version()).