From 955f593cc58d2733aff7ce3d542fdd0e161f1af6 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Wed, 2 Feb 2022 11:34:13 -0400 Subject: [PATCH] form --- city-office/.exercism/config.json | 17 ++ city-office/.exercism/metadata.json | 1 + city-office/.formatter.exs | 4 + city-office/.gitignore | 24 +++ city-office/HELP.md | 75 ++++++++ city-office/HINTS.md | 63 +++++++ city-office/README.md | 173 ++++++++++++++++++ city-office/mix.exs | 28 +++ city-office/test/form_test.exs | 273 ++++++++++++++++++++++++++++ city-office/test/test_helper.exs | 2 + knapsack/.exercism/config.json | 18 ++ knapsack/.exercism/metadata.json | 1 + knapsack/.formatter.exs | 4 + knapsack/HELP.md | 75 ++++++++ knapsack/README.md | 52 ++++++ knapsack/lib/knapsack.ex | 16 ++ knapsack/mix.exs | 28 +++ knapsack/test/knapsack_test.exs | 103 +++++++++++ knapsack/test/test_helper.exs | 2 + 19 files changed, 959 insertions(+) create mode 100644 city-office/.exercism/config.json create mode 100644 city-office/.exercism/metadata.json create mode 100644 city-office/.formatter.exs create mode 100644 city-office/.gitignore create mode 100644 city-office/HELP.md create mode 100644 city-office/HINTS.md create mode 100644 city-office/README.md create mode 100644 city-office/mix.exs create mode 100644 city-office/test/form_test.exs create mode 100644 city-office/test/test_helper.exs create mode 100644 knapsack/.exercism/config.json create mode 100644 knapsack/.exercism/metadata.json create mode 100644 knapsack/.formatter.exs create mode 100644 knapsack/HELP.md create mode 100644 knapsack/README.md create mode 100644 knapsack/lib/knapsack.ex create mode 100644 knapsack/mix.exs create mode 100644 knapsack/test/knapsack_test.exs create mode 100644 knapsack/test/test_helper.exs diff --git a/city-office/.exercism/config.json b/city-office/.exercism/config.json new file mode 100644 index 0000000..d64986a --- /dev/null +++ b/city-office/.exercism/config.json @@ -0,0 +1,17 @@ +{ + "blurb": "Learn about writing documentation and typespecs by getting your code ready for the arrival of a new colleague at the city office.", + "authors": ["angelikatyborska"], + "contributors": ["neenjaw", "michallepicki"], + "files": { + "solution": [ + "lib/form.ex" + ], + "test": [ + "test/form_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "language_versions": ">=1.10" +} diff --git a/city-office/.exercism/metadata.json b/city-office/.exercism/metadata.json new file mode 100644 index 0000000..7872ae0 --- /dev/null +++ b/city-office/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"city-office","id":"7a02c799af3140598102b2a3354947cb","url":"https://exercism.org/tracks/elixir/exercises/city-office","handle":"rjNemo","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/city-office/.formatter.exs b/city-office/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/city-office/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/city-office/.gitignore b/city-office/.gitignore new file mode 100644 index 0000000..4abee35 --- /dev/null +++ b/city-office/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +match_binary-*.tar + diff --git a/city-office/HELP.md b/city-office/HELP.md new file mode 100644 index 0000000..f171b74 --- /dev/null +++ b/city-office/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be skipped. + +Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/form.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- [Exercism's support channel on gitter](https://gitter.im/exercism/support) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. +If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang). \ No newline at end of file diff --git a/city-office/HINTS.md b/city-office/HINTS.md new file mode 100644 index 0000000..01a107f --- /dev/null +++ b/city-office/HINTS.md @@ -0,0 +1,63 @@ +# Hints + +## General + +- Read the official documentation for [typespecs][typespecs]. +- Read the official documentation about [writing documentation][writing-documentation]. +- Read about using module attributes as annotations in the [official Getting Started guide][getting-started-module-attributes]. +- Read about using typespecs in the [official Getting Started guide][getting-started-typespecs]. + +## 1. Document the purpose of the form tools + +- The module attribute `@moduledoc` can be used to write documentation for a module. + +## 2. Document filling out fields with blank values + +- The module attribute `@doc` can be used to write documentation for a function. +- The module attribute `@spec` can be used to write a typespec for a function. +- Place the `@doc` and `@spec` attributes right before the first function clause of the function that those attributes describe. +- Refer to the [typespecs documentation][typespecs-types] for a list of all available types. +- The correct type for strings is [defined in the `String` module][string-t]. + +## 3. Document splitting values into lists of uppercase letters + +- The module attribute `@doc` can be used to write documentation for a function. +- The module attribute `@spec` can be used to write a typespec for a function. +- Place the `@doc` and `@spec` attributes right before the first function clause of the function that those attributes describe. +- Refer to the [typespecs documentation][typespecs-types] for a list of all available types. +- The correct type for strings is [defined in the `String` module][string-t]. +- A list is a parametrized type. + +## 4. Document checking if a value fits a field with a max length + +- The module attribute `@doc` can be used to write documentation for a function. +- The module attribute `@spec` can be used to write a typespec for a function. +- Place the `@doc` and `@spec` attributes right before the first function clause of the function that those attributes describe. +- Refer to the [typespecs documentation][typespecs-types] for a list of all available types. +- The correct type for strings is [defined in the `String` module][string-t]. +- Literal values can be used in a typespec. +- The pipe `|` can be used to represent a union of types. + +## 5. Document different address formats + +- The module attribute `@type` can be use to define a custom public type. +- Types can be compound, e.g. when specifying a type that's a map, you can also specify the types of the values under the specific keys. +- [The type operator `::`][type-operator] can also be used to prepend a variable name to a type. +- Custom types can be used to define other custom types. + +## 6. Document formatting the address + +- The module attribute `@doc` can be used to write documentation for a function. +- The module attribute `@spec` can be used to write a typespec for a function. +- Place the `@doc` and `@spec` attributes right before the first function clause of the function that those attributes describe. +- Refer to the [typespecs documentation][typespecs-types] for a list of all available types. +- The correct type for strings is [defined in the `String` module][string-t]. +- Custom types can be used in a typespec. + +[writing-documentation]: https://hexdocs.pm/elixir/writing-documentation.html +[typespecs]: https://hexdocs.pm/elixir/typespecs.html +[typespecs-types]: https://hexdocs.pm/elixir/typespecs.html#types-and-their-syntax +[getting-started-module-attributes]: https://elixir-lang.org/getting-started/module-attributes.html#as-annotations +[getting-started-typespecs]: https://elixir-lang.org/getting-started/typespecs-and-behaviours.html#types-and-specs +[string-t]: https://hexdocs.pm/elixir/String.html#t:t/0 +[type-operator]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#::/2 \ No newline at end of file diff --git a/city-office/README.md b/city-office/README.md new file mode 100644 index 0000000..e00dfd9 --- /dev/null +++ b/city-office/README.md @@ -0,0 +1,173 @@ +# City Office + +Welcome to City Office on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +## Docs + +Documentation in Elixir is a first-class citizen. + +The two module attributes that commonly used to document your code - `@moduledoc` for documenting a module and `@doc` for documenting a function that follows the attribute. The `@moduledoc` attribute usually appears on the first line of the module, and the `@doc` attribute usually appears right before a function definition, or the function's typespec if it has one. The documentation is commonly written in a multiline string using the heredoc syntax. + +Elixir documentation is written in [**Markdown**][markdown]. + +```elixir +defmodule String do + @moduledoc """ + Strings in Elixir are UTF-8 encoded binaries. + """ + + @doc """ + Converts all characters in the given string to uppercase according to `mode`. + + ## Examples + + iex> String.upcase("abcd") + "ABCD" + + iex> String.upcase("olá") + "OLÁ" + """ + def upcase(string, mode \\ :default) +end +``` + +## Typespecs + +Elixir is a dynamically typed language, which means it doesn't provide compile-time type checks. Still, type specifications can be used as a form of documentation. + +A type specification can be added to a function using the `@spec` module attribute right before the function definition. `@spec` is followed by the function name and a list of all of its arguments' types, in parentheses, separated by commas. The type of the return value is separated from the function's arguments with a double colon `::`. + +```elixir +@spec longer_than?(String.t(), non_neg_integer()) :: boolean() +def longer_than?(string, length), do: String.length(string) > length +``` + +### Types + +Most commonly used types include: + +- booleans: `boolean()` +- strings: `String.t()` +- numbers: `integer()`, `non_neg_integer()`, `pos_integer()`, `float()` +- lists: `list()` +- a value of any type: `any()` + +Some types can also be parameterized, for example `list(integer)` is a list of integers. + +Literal values can also be used as types. + +A union of types can be written using the pipe `|`. For example, `integer() | :error` means either an integer or the atom literal `:error`. + +A full list of all types can be found in the ["Typespecs" section in the official documentation][types]. + +### Naming arguments + +Arguments in the typespec could also be named which is useful for distinguishing multiple arguments of the same type. The argument name, followed by a double colon, goes before the argument's type. + +```elixir +@spec to_hex({hue :: integer, saturation :: integer, lightness :: integer}) :: String.t() +``` + +### Custom types + +Typespecs aren't limited to just the built-in types. Custom types can be defined using the `@type` module attribute. A custom type definition starts with the type's name, followed by a double colon and then the type itself. + +```elixir +@type color :: {hue :: integer, saturation :: integer, lightness :: integer} + +@spec to_hex(color()) :: String.t() +``` + +A custom type can be used from the same module where it's defined, or from another module. + +[types]: https://hexdocs.pm/elixir/typespecs.html#types-and-their-syntax +[markdown]: https://docs.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax + +## Instructions + +You have been working in the city office for a while, and you have developed a set of tools that speed up your day-to-day work, for example with filling out forms. + +Now, a new colleague is joining you, and you realized your tools might not be self-explanatory. There are a lot of weird conventions in your office, like always filling out forms with uppercase letters and avoiding leaving fields empty. + +You decide to write some documentation so that it's easier for your new colleague to hop right in and start using your tools. + +## 1. Document the purpose of the form tools + +Add documentation to the `Form` module that describes its purpose. It should read: + +``` +A collection of loosely related functions helpful for filling out various forms at the city office. +``` + +## 2. Document filling out fields with blank values + +Add documentation and a typespec to the `Form.blanks/1` function. The documentation should read: + +``` +Generates a string of a given length. + +This string can be used to fill out a form field that is supposed to have no value. +Such fields cannot be left empty because a malicious third party could fill them out with false data. +``` + +The typespec should explain that the function accepts a single argument, a non-negative integer, and returns a string. + +## 3. Document splitting values into lists of uppercase letters + +Add documentation and a typespec to the `Form.letters/1` function. The documentation should read: + +``` +Splits the string into a list of uppercase letters. + +This is needed for form fields that don't offer a single input for the whole string, +but instead require splitting the string into a predefined number of single-letter inputs. +``` + +The typespec should explain that the function accepts a single argument, a string, and returns a list of strings. + +## 4. Document checking if a value fits a field with a max length + +Add documentation and a typespec to the `Form.check_length/2` function. The documentation should read: + +``` +Checks if the value has no more than the maximum allowed number of letters. + +This is needed to check that the values of fields do not exceed the maximum allowed length. +It also tells you by how much the value exceeds the maximum. +``` + +The typespec should explain that the function accepts two arguments, a string and a non-negative integer, and returns one of two possible values. It returns either the `:ok` atom or a 2-tuple with the first element being the `:error` atom, and the second a positive integer. + +## 5. Document different address formats + +For some unknown to you reason, the city office's internal system uses two different ways of representing addresses - either as a map or as a tuple. + +Document this fact by defining three custom public types: +- `address_map` - a map with the keys `:street`, `:postal_code`, and `:city`. Each key holds a value of type string. +- `address_tuple` - a tuple with three values - `street`, `postal_code`, and `city`. Each value is of type string. Differentiate the values by giving them names in the typespec. +- `address` - can be either an `address_map` or an `address_tuple`. + +## 6. Document formatting the address + +Add documentation and a typespec to the `Form.format_address/1` function. The documentation should read: + +``` +Formats the address as an uppercase multiline string. +``` + +The typespec should explain that the function accepts one argument, an address, and returns a string. + +## Source + +### Created by + +- @angelikatyborska + +### Contributed to by + +- @neenjaw +- @michallepicki \ No newline at end of file diff --git a/city-office/mix.exs b/city-office/mix.exs new file mode 100644 index 0000000..9e8d828 --- /dev/null +++ b/city-office/mix.exs @@ -0,0 +1,28 @@ +defmodule Form.MixProject do + use Mix.Project + + def project do + [ + app: :city_office, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/city-office/test/form_test.exs b/city-office/test/form_test.exs new file mode 100644 index 0000000..83dee5d --- /dev/null +++ b/city-office/test/form_test.exs @@ -0,0 +1,273 @@ +defmodule FormTest do + use ExUnit.Case + + # Dear Elixir learner, + # If you're reading this test suite to gain some insights, + # please be advised that it is somewhat unusual. + # + # Don't worry if you don't understand this test suite at this stage of your learning journey. + # We had to use some advanced features to be able to write assertions about docs and typespecs. + # You wouldn't normally write assertions for that in a typical codebase. + # We're doing it here strictly for educational purposes. + + defmacrop assert_moduledoc(expected_moduledoc) do + quote do + {:docs_v1, _, _, _, module_doc, _, _} = Code.fetch_docs(Form) + + if module_doc == :none do + flunk("expected the module Form to have documentation") + else + actual_moduledoc = module_doc["en"] + assert actual_moduledoc == unquote(expected_moduledoc) + end + end + end + + defmacrop assert_doc({function_name, function_arity}, expected_doc) do + quote do + {:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(Form) + + {_, _, _, doc_content, _} = + Enum.find(docs, fn {{kind, function_name, arity}, _, _, _, _} -> + {kind, function_name, arity} == + {:function, unquote(function_name), unquote(function_arity)} + end) + + if doc_content == :none do + flunk( + "expected the function Form.#{unquote(function_name)}/#{unquote(function_arity)} to have documentation" + ) + else + actual_doc = doc_content["en"] + assert actual_doc == unquote(expected_doc) + end + end + end + + defmacrop assert_spec({function_name, function_arity}, arguments_specs, return_spec) do + quote do + {:ok, specs} = Code.Typespec.fetch_specs(Form) + + spec = + Enum.find(specs, fn {{function_name, arity}, _} -> + {function_name, arity} == {unquote(function_name), unquote(function_arity)} + end) + + assert spec, + "expected the function Form.#{unquote(function_name)}/#{unquote(function_arity)} to have a typespec" + + {{unquote(function_name), unquote(function_arity)}, [{:type, _, :fun, _} = function_spec]} = + spec + + {:"::", _, [arguments, return]} = + Code.Typespec.spec_to_quoted(unquote(function_name), function_spec) + + accepted_arguments_specs = + Enum.map(unquote(arguments_specs), fn arguments_spec -> + "#{unquote(function_name)}(#{arguments_spec})" + end) + + actual_arguments_spec = Macro.to_string(arguments) + assert actual_arguments_spec in accepted_arguments_specs + + expected_return_spec = unquote(return_spec) + actual_return_spec = Macro.to_string(return) + assert actual_return_spec == expected_return_spec + end + end + + defmacrop assert_type({module_name, type_name}, expected_type_definition) do + quote do + {:ok, types} = Code.Typespec.fetch_types(unquote(module_name)) + + type = + Enum.find(types, fn {declaration, {type_name, _, _}} -> + declaration == :type && type_name == unquote(type_name) + end) + + {:type, type} = type + + {:"::", _, [_, type_definition]} = Code.Typespec.type_to_quoted(type) + + assert type, + "expected the module#{unquote(module_name)} to have a public type named #{unquote(type_name)}" + + actual_type_definition = Macro.to_string(type_definition) + assert actual_type_definition == unquote(expected_type_definition) + end + end + + describe "the Form module" do + @tag task_id: 1 + test "has documentation" do + expected_moduledoc = """ + A collection of loosely related functions helpful for filling out various forms at the city office. + """ + + assert_moduledoc(expected_moduledoc) + end + end + + describe "blanks/1" do + @tag task_id: 2 + test "returns a string with Xs of a given length" do + assert Form.blanks(5) == "XXXXX" + end + + @tag task_id: 2 + test "returns an empty string when given length is 0" do + assert Form.blanks(0) == "" + end + + @tag task_id: 2 + test "has documentation" do + expected_doc = """ + Generates a string of a given length. + + This string can be used to fill out a form field that is supposed to have no value. + Such fields cannot be left empty because a malicious third party could fill them out with false data. + """ + + assert_doc({:blanks, 1}, expected_doc) + end + + @tag task_id: 2 + test "has a correct spec" do + assert_spec({:blanks, 1}, ["n :: non_neg_integer()", "non_neg_integer()"], "String.t()") + end + end + + describe "letters/1" do + @tag task_id: 3 + test "returns a list of upcase letters" do + assert Form.letters("Sao Paulo") == ["S", "A", "O", " ", "P", "A", "U", "L", "O"] + end + + @tag task_id: 3 + test "returns an empty list when given an empty string" do + assert Form.letters("") == [] + end + + @tag task_id: 3 + test "has documentation" do + expected_doc = """ + Splits the string into a list of uppercase letters. + + This is needed for form fields that don't offer a single input for the whole string, + but instead require splitting the string into a predefined number of single-letter inputs. + """ + + assert_doc({:letters, 1}, expected_doc) + end + + @tag task_id: 3 + test "has a typespec" do + assert_spec({:letters, 1}, ["word :: String.t()", "String.t()"], "[String.t()]") + end + end + + describe "check_length/2" do + @tag task_id: 4 + test "returns :ok is value is below max length" do + assert Form.check_length("Ruiz", 6) == :ok + end + + @tag task_id: 4 + test "returns :ok is value is of exactly max length" do + assert Form.check_length("Martinez-Cooper", 15) == :ok + end + + @tag task_id: 4 + test "returns an error tuple with the difference between max length and actual length" do + assert Form.check_length("Martinez-Campbell", 10) == {:error, 7} + end + + @tag task_id: 4 + test "has documentation" do + expected_doc = """ + Checks if the value has no more than the maximum allowed number of letters. + + This is needed to check that the values of fields do not exceed the maximum allowed length. + It also tells you by how much the value exceeds the maximum. + """ + + assert_doc({:check_length, 2}, expected_doc) + end + + @tag task_id: 4 + test "has a typespec" do + assert_spec( + {:check_length, 2}, + ["word :: String.t(), length :: non_neg_integer()", "String.t(), non_neg_integer()"], + ":ok | {:error, pos_integer()}" + ) + end + end + + describe "custom types in the Form module" do + @tag task_id: 5 + test "has a custom 'address_map' type" do + expected_type_definition = + "%{street: String.t(), postal_code: String.t(), city: String.t()}" + + assert_type({Form, :address_map}, expected_type_definition) + end + + @tag task_id: 5 + test "has a custom 'address_tuple' type with named arguments" do + expected_type_definition = + "{street :: String.t(), postal_code :: String.t(), city :: String.t()}" + + assert_type({Form, :address_tuple}, expected_type_definition) + end + + @tag task_id: 5 + test "has a custom 'address' type that is a union of 'address_map' and 'address_tuple'" do + expected_type_definition = "address_map() | address_tuple()" + + assert_type({Form, :address}, expected_type_definition) + end + end + + describe "format_address/1" do + @tag task_id: 6 + test "accepts a map" do + input = %{ + street: "Wiejska 4/6/8", + postal_code: "00-902", + city: "Warsaw" + } + + result = """ + WIEJSKA 4/6/8 + 00-902 WARSAW + """ + + assert Form.format_address(input) == result + end + + @tag task_id: 6 + test "accepts a 3 string tuple" do + result = """ + PLATZ DER REPUBLIK 1 + 11011 BERLIN + """ + + assert Form.format_address({"Platz der Republik 1", "11011", "Berlin"}) == result + end + + @tag task_id: 6 + test "has documentation" do + expected_doc = """ + Formats the address as an uppercase multiline string. + """ + + assert_doc({:format_address, 1}, expected_doc) + end + + @tag task_id: 6 + test "has a typespec" do + assert_spec({:format_address, 1}, ["address :: address()", "address()"], "String.t()") + end + end +end diff --git a/city-office/test/test_helper.exs b/city-office/test/test_helper.exs new file mode 100644 index 0000000..e8677a3 --- /dev/null +++ b/city-office/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/knapsack/.exercism/config.json b/knapsack/.exercism/config.json new file mode 100644 index 0000000..89832a2 --- /dev/null +++ b/knapsack/.exercism/config.json @@ -0,0 +1,18 @@ +{ + "authors": ["jiegillet"], + "contributors": [], + "files": { + "example": [ + ".meta/example.ex" + ], + "solution": [ + "lib/knapsack.ex" + ], + "test": [ + "test/knapsack_test.exs" + ] + }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" +} diff --git a/knapsack/.exercism/metadata.json b/knapsack/.exercism/metadata.json new file mode 100644 index 0000000..35eafbe --- /dev/null +++ b/knapsack/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"knapsack","id":"5936ec9e65e44acd831c1a9abc93b24d","url":"https://exercism.org/tracks/elixir/exercises/knapsack","handle":"rjNemo","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/knapsack/.formatter.exs b/knapsack/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/knapsack/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/knapsack/HELP.md b/knapsack/HELP.md new file mode 100644 index 0000000..164fcc9 --- /dev/null +++ b/knapsack/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be skipped. + +Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/knapsack.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- [Exercism's support channel on gitter](https://gitter.im/exercism/support) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. +If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang). \ No newline at end of file diff --git a/knapsack/README.md b/knapsack/README.md new file mode 100644 index 0000000..d768bab --- /dev/null +++ b/knapsack/README.md @@ -0,0 +1,52 @@ +# Knapsack + +Welcome to Knapsack on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +In this exercise, let's try to solve a classic problem. + +Bob is a thief. After months of careful planning, he finally manages to +crack the security systems of a high-class apartment. + +In front of him are many items, each with a value (v) and weight (w). Bob, +of course, wants to maximize the total value he can get; he would gladly +take all of the items if he could. However, to his horror, he realizes that +the knapsack he carries with him can only hold so much weight (W). + +Given a knapsack with a specific carrying capacity (W), help Bob determine +the maximum value he can get from the items in the house. Note that Bob can +take only one of each item. + +All values given will be strictly positive. Items will be represented as a +list of pairs, `wi` and `vi`, where the first element `wi` is the weight of +the *i*th item and `vi` is the value for that item. + +For example: + +Items: [ + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } +] + +Knapsack Limit: 10 + +For the above, the first item has weight 5 and value 10, the second item has +weight 4 and value 40, and so on. + +In this example, Bob should take the second and fourth item to maximize his +value, which, in this case, is 90. He cannot get more than 90 as his +knapsack has a weight limit of 10. + +## Source + +### Created by + +- @jiegillet + +### Based on + +Wikipedia - https://en.wikipedia.org/wiki/Knapsack_problem \ No newline at end of file diff --git a/knapsack/lib/knapsack.ex b/knapsack/lib/knapsack.ex new file mode 100644 index 0000000..a0d9f7c --- /dev/null +++ b/knapsack/lib/knapsack.ex @@ -0,0 +1,16 @@ +defmodule Knapsack do + @doc """ + Return the maximum value that a knapsack can carry. + """ + @spec maximum_value(items :: [%{value: integer, weight: integer}], maximum_weight :: integer) :: + integer + def maximum_value([], _), do: 0 + + def maximum_value([%{weight: weight}], maximum_weight) + when weight > maximum_weight, + do: 0 + + def maximum_value(items, maximum_weight) do + items |> Enum.filter(fn %{weight: weight} -> weight < maximum_weight end) + end +end diff --git a/knapsack/mix.exs b/knapsack/mix.exs new file mode 100644 index 0000000..e883380 --- /dev/null +++ b/knapsack/mix.exs @@ -0,0 +1,28 @@ +defmodule Knapsack.MixProject do + use Mix.Project + + def project do + [ + app: :knapsack, + version: "0.1.0", + # elixir: "~> 1.8", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/knapsack/test/knapsack_test.exs b/knapsack/test/knapsack_test.exs new file mode 100644 index 0000000..9d8590a --- /dev/null +++ b/knapsack/test/knapsack_test.exs @@ -0,0 +1,103 @@ +defmodule KnapsackTest do + use ExUnit.Case + + test "no items" do + items = [] + maximum_weight = 100 + + assert Knapsack.maximum_value(items, maximum_weight) == 0 + end + + test "one item, too heavy" do + items = [%{value: 1, weight: 100}] + maximum_weight = 10 + + assert Knapsack.maximum_value(items, maximum_weight) == 0 + end + + test "five items (cannot be greedy by weight)" do + items = [ + %{value: 5, weight: 2}, + %{value: 5, weight: 2}, + %{value: 5, weight: 2}, + %{value: 5, weight: 2}, + %{value: 21, weight: 10} + ] + + maximum_weight = 10 + + assert Knapsack.maximum_value(items, maximum_weight) == 21 + end + + @tag :pending + test "five items (cannot be greedy by value)" do + items = [ + %{value: 20, weight: 2}, + %{value: 20, weight: 2}, + %{value: 20, weight: 2}, + %{value: 20, weight: 2}, + %{value: 50, weight: 10} + ] + + maximum_weight = 10 + + assert Knapsack.maximum_value(items, maximum_weight) == 80 + end + + @tag :pending + test "example knapsack" do + items = [ + %{value: 10, weight: 5}, + %{value: 40, weight: 4}, + %{value: 30, weight: 6}, + %{value: 50, weight: 4} + ] + + maximum_weight = 10 + + assert Knapsack.maximum_value(items, maximum_weight) == 90 + end + + @tag :pending + test "8 items" do + items = [ + %{value: 350, weight: 25}, + %{value: 400, weight: 35}, + %{value: 450, weight: 45}, + %{value: 20, weight: 5}, + %{value: 70, weight: 25}, + %{value: 8, weight: 3}, + %{value: 5, weight: 2}, + %{value: 5, weight: 2} + ] + + maximum_weight = 104 + + assert Knapsack.maximum_value(items, maximum_weight) == 900 + end + + @tag :pending + test "15 items" do + items = [ + %{value: 135, weight: 70}, + %{value: 139, weight: 73}, + %{value: 149, weight: 77}, + %{value: 150, weight: 80}, + %{value: 156, weight: 82}, + %{value: 163, weight: 87}, + %{value: 173, weight: 90}, + %{value: 184, weight: 94}, + %{value: 192, weight: 98}, + %{value: 201, weight: 106}, + %{value: 210, weight: 110}, + %{value: 214, weight: 113}, + %{value: 221, weight: 115}, + %{value: 229, weight: 118}, + %{value: 240, weight: 120} + ] + + maximum_weight = 750 + + assert Knapsack.maximum_value(items, maximum_weight) == 1458 + end +end diff --git a/knapsack/test/test_helper.exs b/knapsack/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/knapsack/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)