Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

the palindrome-products exercise #270

Merged
merged 1 commit into from
Aug 6, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 21 additions & 0 deletions exercises/palindrome-products/HINTS.md
Original file line number Diff line number Diff line change
@@ -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.
76 changes: 76 additions & 0 deletions exercises/palindrome-products/README.md
Original file line number Diff line number Diff line change
@@ -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.

30 changes: 30 additions & 0 deletions exercises/palindrome-products/rebar.config
Original file line number Diff line number Diff line change
@@ -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]}.
90 changes: 90 additions & 0 deletions exercises/palindrome-products/src/example.erl
Original file line number Diff line number Diff line change
@@ -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<Min ->
Best;

largest(Min, Max, Cur1, Cur2, Best) when Cur2<Min ->
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<BestProduct ->
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)=<min(B1, B2)
end,
Factors
).


test_version() -> 1.
9 changes: 9 additions & 0 deletions exercises/palindrome-products/src/palindrome_products.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{application, palindrome_products,
[{description, "exercism.io - palindrome-products"},
{vsn, "0.0.1"},
{modules, []},
{registered, []},
{applications, [kernel,
stdlib]},
{env, []}
]}.
11 changes: 11 additions & 0 deletions exercises/palindrome-products/src/palindrome_products.erl
Original file line number Diff line number Diff line change
@@ -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.
63 changes: 63 additions & 0 deletions exercises/palindrome-products/test/palindrome_products_tests.erl
Original file line number Diff line number Diff line change
@@ -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()).