diff --git a/config.json b/config.json index f80b140e3fd..67e26edfdb1 100644 --- a/config.json +++ b/config.json @@ -1564,6 +1564,18 @@ "topics": [ "arrays" ] + }, + { + "slug": "knapsack", + "uuid": "b0301d0b-d97a-4043-bd82-ba1edf8c1b16", + "core": false, + "unlocked_by": "clock", + "difficulty": 5, + "topics": [ + "algorithms", + "lists", + "loops" + ] } ] } diff --git a/exercises/knapsack/README.md b/exercises/knapsack/README.md new file mode 100644 index 00000000000..0a21d980a5c --- /dev/null +++ b/exercises/knapsack/README.md @@ -0,0 +1,87 @@ +# Knapsack + +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. + + +## Exception messages + +Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to +indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not +every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include +a message. + +To raise a message with an exception, just write it as an argument to the exception type. For example, instead of +`raise Exception`, you should write: + +```python +raise Exception("Meaningful message indicating the source of the error") +``` + +## Running the tests + +To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)): + +- Python 2.7: `py.test knapsack_test.py` +- Python 3.4+: `pytest knapsack_test.py` + +Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): +`python -m pytest knapsack_test.py` + +### Common `pytest` options + +- `-v` : enable verbose output +- `-x` : stop running tests on first failure +- `--ff` : run failures from previous test before running other test cases + +For other options, see `python -m pytest -h` + +## Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/knapsack` directory. + +You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`. + +For more detailed information about running tests, code style and linting, +please see [Running the Tests](http://exercism.io/tracks/python/tests). + +## Source + +Wikipedia [https://en.wikipedia.org/wiki/Knapsack_problem](https://en.wikipedia.org/wiki/Knapsack_problem) + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/knapsack/example.py b/exercises/knapsack/example.py new file mode 100644 index 00000000000..f19d066e951 --- /dev/null +++ b/exercises/knapsack/example.py @@ -0,0 +1,11 @@ +def solve_knapsack(max_weight, items): + t = [[0 for _ in range(len(items) + 1)] for _ in range(max_weight + 1)] + for weight in range(1, max_weight + 1): + for i, item in enumerate(items, 1): + if item["weight"] <= weight: + value = item["value"] + t[weight - item["weight"]][i - 1] + value_without_item = t[weight][i - 1] + t[weight][i] = max(value, value_without_item) + else: + t[weight][i] = t[weight][i - 1] + return t[max_weight][len(items)] diff --git a/exercises/knapsack/knapsack.py b/exercises/knapsack/knapsack.py new file mode 100644 index 00000000000..9458bb3c2af --- /dev/null +++ b/exercises/knapsack/knapsack.py @@ -0,0 +1,2 @@ +def solve_knapsack(max_weight, items): + pass diff --git a/exercises/knapsack/knapsack_test.py b/exercises/knapsack/knapsack_test.py new file mode 100644 index 00000000000..14be85f6ad6 --- /dev/null +++ b/exercises/knapsack/knapsack_test.py @@ -0,0 +1,66 @@ +import unittest + +from knapsack import solve_knapsack + + +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 + +class ChangeTest(unittest.TestCase): + def test_no_items(self): + self.assertEqual(solve_knapsack(100, []), 0) + + def test_one_item_too_heavy(self): + self.assertEqual(solve_knapsack(10, [{"weight": 100, "value": 1}]), 0) + + def test_cannot_be_greedy_by_weight(self): + self.assertEqual(solve_knapsack(10, + [{"weight": 2, "value": 5}, + {"weight": 2, "value": 5}, + {"weight": 2, "value": 5}, + {"weight": 2, "value": 5}, + {"weight": 10, "value": 21}]), 21) + + def test_cannot_be_greedy_by_value(self): + self.assertEqual(solve_knapsack(10, [{"weight": 2, "value": 20}, + {"weight": 2, "value": 20}, + {"weight": 2, "value": 20}, + {"weight": 2, "value": 20}, + {"weight": 10, "value": 50}]), 80) + + def test_example_knapsack(self): + self.assertEqual(solve_knapsack(10, [{"weight": 5, "value": 10}, + {"weight": 4, "value": 40}, + {"weight": 6, "value": 30}, + {"weight": 4, "value": 50}]), 90) + + def test_eight_items(self): + self.assertEqual(solve_knapsack(104, [{"weight": 25, "value": 350}, + {"weight": 35, "value": 400}, + {"weight": 45, "value": 450}, + {"weight": 5, "value": 20}, + {"weight": 25, "value": 70}, + {"weight": 3, "value": 8}, + {"weight": 2, "value": 5}, + {"weight": 2, "value": 5}]), 900) + + def test_fifteen_items(self): + self.assertEqual(solve_knapsack(750, + [{"weight": 70, "value": 135}, + {"weight": 73, "value": 139}, + {"weight": 77, "value": 149}, + {"weight": 80, "value": 150}, + {"weight": 82, "value": 156}, + {"weight": 87, "value": 163}, + {"weight": 90, "value": 173}, + {"weight": 94, "value": 184}, + {"weight": 98, "value": 192}, + {"weight": 106, "value": 201}, + {"weight": 110, "value": 210}, + {"weight": 113, "value": 214}, + {"weight": 115, "value": 221}, + {"weight": 118, "value": 229}, + {"weight": 120, "value": 240}]), 1458) + + +if __name__ == "__main__": + unittest.main()