-
-
Notifications
You must be signed in to change notification settings - Fork 405
New practice exercise killer-sudoku-helper
#1116
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
New practice exercise killer-sudoku-helper
#1116
Conversation
|
Thank you for contributing to Based on the files changed in this PR, it would be good to pay attention to the following details when reviewing the PR:
Automated comment created by PR Commenter 🤖. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great to me. Fun short exercise! It can be merged as it is, or you can consider my proposal to decrease the difficulty first.
| defmodule KillerSudokuHelper do | ||
| @doc """ | ||
| Return the possible combinations of `size` distinct numbers from 1-9 excluding `exclude` that sum up to `sum`. | ||
| """ | ||
| @spec combinations(cage :: %{exclude: [integer], size: integer, sum: integer}) :: [[integer]] | ||
| def combinations(%{exclude: exclude, size: size, sum: sum}) do | ||
| numbers = [9, 8, 7, 6, 5, 4, 3, 2, 1] -- exclude | ||
|
|
||
| do_combinations(numbers, size, [[]]) | ||
| |> Enum.filter(&(Enum.sum(&1) == sum)) | ||
| end | ||
|
|
||
| defp do_combinations(_numbers, 0, list), do: list | ||
|
|
||
| defp do_combinations(numbers, size, list) do | ||
| numbers | ||
| |> tails() | ||
| |> Enum.map(fn [head | tail] -> | ||
| do_combinations(tail, size - 1, Enum.map(list, &[head | &1])) | ||
| end) | ||
| |> Enum.concat() | ||
| end | ||
|
|
||
| defp tails([]), do: [] | ||
| defp tails([_ | tail] = list), do: [list | tails(tail)] | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My solution, done without looking at yours first:
defmodule KillerSudokuHelper do
@doc """
Return the possible combinations of `size` distinct numbers from 1-9 excluding `exclude` that sum up to `sum`.
"""
@spec combinations(cage :: %{exclude: [integer], size: integer, sum: integer}) :: [[integer]]
def combinations(cage) do
%{sum: sum, size: size, exclude: exclude} = cage
sorted_numbers = Enum.to_list(1..9) -- exclude
all_combinations = do_all_combinations(sorted_numbers, size)
Enum.filter(all_combinations, &(Enum.sum(&1) == sum))
end
defp do_all_combinations(sorted_numbers, 1), do: Enum.map(sorted_numbers, & [&1])
defp do_all_combinations(sorted_numbers, size) do
Enum.flat_map(sorted_numbers, fn n ->
sorted_numbers
|> Enum.filter(&(&1 > n))
|> do_all_combinations(size - 1)
|> Enum.map(&[n | &1])
end)
end
endThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very very similar, nice :)
config.json
Outdated
| "pattern-matching", | ||
| "recursion" | ||
| ], | ||
| "practices": [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, it could practice recursion or enum, but those are already full.
config.json
Outdated
| "recursion" | ||
| ], | ||
| "practices": [], | ||
| "difficulty": 6 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other difficulty 6 exercises are:
knapsack
list-ops
markdown
ocr-numbers
robot-simulator
I found this one much easier than those to be honest. Maybe a 4?
| "difficulty": 6 | |
| "difficulty": 4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I agree.
Originally I placed it by knapsack because the problem is very very similar, however for this one, brute force is the best option since the size of the digits to chose is so constrained.
| @tag :pending | ||
| test "Cage with several combinations that is restricted" do | ||
| cage = %{exclude: [1, 4], size: 2, sum: 10} | ||
| assert KillerSudokuHelper.combinations(cage) == [[2, 8], [3, 7]] | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found it much easier to start with this test instead of "Cage with sum 45 contains all digits 1:9" once I got the basic 1-element cases done because the exclude part was much easier to figure out correctly (-- exclude) than the recursion part 🤷 but I guess we will just stick to the order in the problem specs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you usually solve these? Do you take it one test at a time the prescribed Exercism way?
I always do --include pending --seed 0 --listen-on-stdin and look at whichever failure interests me.
In that sense I never think too much about the order of the tests, but I don't mind switching tests around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I first run all tests on my initial attempt. If a lot of them fail, I go in order because most exercises have tests implemented in a good order for fulfilling them. I usually don't look at all the failures at once if it's more than 2.
As a maintainer, I prefer to keep the order of tests as it is in problem specs. That makes it easier to update the exercise later 😅
| """ | ||
| @spec combinations(cage :: %{exclude: [integer], size: integer, sum: integer}) :: [[integer]] | ||
| def combinations(%{exclude: exclude, size: size, sum: sum}) do | ||
| numbers = [9, 8, 7, 6, 5, 4, 3, 2, 1] -- exclude |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you removed the descending range to make it pass in older Elixirs, but I would have still kept it, just with a Enum.sort instead. But it doesn't matter 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I first had 9..1//-1, and when it got rejected I wrote the full list explicitly out of spite :D
Keeping the momentum :)