Skip to content

Commit 7e8509d

Browse files
authored
Add Knapsack exercise (#1644)
1 parent d408ec0 commit 7e8509d

File tree

8 files changed

+314
-0
lines changed

8 files changed

+314
-0
lines changed

config.json

+12
Original file line numberDiff line numberDiff line change
@@ -1555,6 +1555,18 @@
15551555
"enumeration"
15561556
],
15571557
"difficulty": 8
1558+
},
1559+
{
1560+
"slug": "knapsack",
1561+
"name": "Knapsack",
1562+
"uuid": "91b585ce-b476-4c0d-aa2b-c1487a6e466f",
1563+
"practices": [],
1564+
"prerequisites": [
1565+
"numbers",
1566+
"arrays",
1567+
"enumeration"
1568+
],
1569+
"difficulty": 8
15581570
}
15591571
]
15601572
},
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Hints
2+
3+
## A starting point: brute-force recursion
4+
5+
If you're stuck, a good starting point is a brute-force recursive solution.
6+
You can see it sketched out in the first half of the article ["Demystifying the 0-1 knapsack problem: top solutions explained"](demystifying-the-knapsack-problem).
7+
8+
## Dynamic programming: what is it?
9+
10+
For a more efficient solution, you can improve your recursive solution using *dynamic programming*, which is introduced in the second half of [the above-mentioned article](demystifying-the-knapsack-problem).
11+
12+
For a more general explainer, see the video ["5 Simple Steps for Solving Dynamic Programming Problems"](solving-dynamic-programming-problems)
13+
14+
## Dynamic programming and the knapsack problem
15+
16+
If you need a more visual walkthrough of how to apply dynamic programming to the knapsack problem, see the video ["0/1 Knapsack problem | Dynamic Programming"](0-1-knapsack-problem).
17+
18+
Also worth mentioning is [this answer](intuition-of-dp-for-knapsack-problem) to a question on Reddit, *"What is the intuition behind Knapsack problem solution using dynamic programming?"*.
19+
Here is the answer in full:
20+
21+
> The intuition behind the solution is basically like any dynamic programming solution: split the task into many sub-tasks, and save solutions to these sub-tasks for later use.
22+
>
23+
> In this case the sub task is to **"Try to fit x items into a knapsack of a smaller size"** instead of trying all possible variations in the whole thing right away.
24+
>
25+
> The idea here is that at any point you can ask, *"Does this item fit into the sack at all?"*
26+
> If not, you repeat by looking at a bigger portion of the sack until you reach the whole size of it.
27+
> If the item still doesn't fit, then it's simply not part of any solution.
28+
>
29+
> If it does fit, however, then there are two options.
30+
> Either the maximum value for that portion of the sack is achieved without the item, or with the item.
31+
> If the former is true then we can just take the previous solution because we already tried the previous items.
32+
> (For example, if we try item 4 and it doesn't increase our maximum then we can just use our previous solution for items 1-3.)
33+
> If the latter is true then we put item 4 in, which takes some value off of our capacity.
34+
> The remaining capacity gets filled with a previous solution.
35+
> How?
36+
> Well, we already tried smaller capacities beforehand, so there should be a solution for that smaller, in this case remaining, capacity.
37+
>
38+
> So the idea is to split the entire knapsack problem into smaller knapsack problems.
39+
> Instead of testing 10 items with capacity 50, you first try (after the trivial case of 0) 1 item and capacity 10, 20, 30, 40 and 50 (or however many sub tasks you want to create) and then take another item and start again at capacity 10.
40+
>
41+
> If you see item 1 fits into capacity 20+, then all these slots in the table now contain this value.
42+
> Then you look at item 2 from capacity 10-50 again.
43+
> Let's assume item 2 fits into capacity 20 as well.
44+
> Then now you check whether it is a new maximum or not, and if it is, then you update the table.
45+
> Now you look at capacity 30 for item 2.
46+
> You see that item 2 fits; this means 10 capacity would remain if you take it.
47+
> However there, as of now, was no item that fits into 10 capacity, thus the solution remains the same as before.
48+
> At 40 this changes: you now realize that even if you include item 2 there are 20 capacity remaining, thus you can fill that space with the previous solution, which was item 1.
49+
> Thus for 40 capacity, as of now, the optimal solution is to take item 1 and 2.
50+
> And so on.
51+
52+
[demystifying-the-knapsack-problem]: https://www.educative.io/blog/0-1-knapsack-problem-dynamic-solution
53+
[solving-dynamic-programming-problems]: https://www.youtube.com/watch?v=aPQY__2H3tE
54+
[0-1-knapsack-problem]: https://www.youtube.com/watch?v=cJ21moQpofY
55+
[intuition-of-dp-for-knapsack-problem]: https://www.reddit.com/r/explainlikeimfive/comments/junw6n/comment/gces429
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Instructions
2+
3+
In this exercise, let's try to solve a classic problem.
4+
5+
Bob is a thief.
6+
After months of careful planning, he finally manages to crack the security systems of a high-class apartment.
7+
8+
In front of him are many items, each with a value (v) and weight (w).
9+
Bob, of course, wants to maximize the total value he can get; he would gladly take all of the items if he could.
10+
However, to his horror, he realizes that the knapsack he carries with him can only hold so much weight (W).
11+
12+
Given a knapsack with a specific carrying capacity (W), help Bob determine the maximum value he can get from the items in the house.
13+
Note that Bob can take only one of each item.
14+
15+
All values given will be strictly positive.
16+
Items will be represented as a list of items.
17+
Each item will have a weight and value.
18+
19+
For example:
20+
21+
```none
22+
Items: [
23+
{ "weight": 5, "value": 10 },
24+
{ "weight": 4, "value": 40 },
25+
{ "weight": 6, "value": 30 },
26+
{ "weight": 4, "value": 50 }
27+
]
28+
29+
Knapsack Limit: 10
30+
```
31+
32+
For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on.
33+
34+
In this example, Bob should take the second and fourth item to maximize his value, which, in this case, is 90.
35+
He cannot get more than 90 as his knapsack has a weight limit of 10.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": ["fpsvogel"],
3+
"files": {
4+
"solution": [
5+
"knapsack.rb"
6+
],
7+
"test": [
8+
"knapsack_test.rb"
9+
],
10+
"example": [
11+
".meta/example.rb"
12+
]
13+
},
14+
"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.",
15+
"source": "Wikipedia",
16+
"source_url": "https://en.wikipedia.org/wiki/Knapsack_problem"
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This solution uses dynamic programming to memoize solutions to overlapping
2+
# subproblems, so that they don't need to be recomputed. It's essentially a
3+
# recursive solution that remembers best-so-far outputs of previous inputs. The
4+
# algorithm has a time complexity of O(n * W), where n is the number of items
5+
# and W is the knapsack's maximum weight.
6+
class Knapsack
7+
def initialize(max_weight)
8+
@max_weight = max_weight
9+
end
10+
11+
def max_value(items)
12+
# e.g. max_values[3] is the maximum value so far for a maximum weight of 3.
13+
max_values = Array.new(@max_weight + 1, 0)
14+
15+
items.each do |item|
16+
@max_weight.downto(item.weight) do |weight|
17+
value_with_item = max_values[weight - item.weight] + item.value
18+
19+
max_values[weight] = [max_values[weight], value_with_item].max
20+
end
21+
end
22+
23+
max_values[@max_weight]
24+
end
25+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[a4d7d2f0-ad8a-460c-86f3-88ba709d41a7]
13+
description = "no items"
14+
include = false
15+
16+
[3993a824-c20e-493d-b3c9-ee8a7753ee59]
17+
description = "no items"
18+
reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7"
19+
20+
[1d39e98c-6249-4a8b-912f-87cb12e506b0]
21+
description = "one item, too heavy"
22+
23+
[833ea310-6323-44f2-9d27-a278740ffbd8]
24+
description = "five items (cannot be greedy by weight)"
25+
26+
[277cdc52-f835-4c7d-872b-bff17bab2456]
27+
description = "five items (cannot be greedy by value)"
28+
29+
[81d8e679-442b-4f7a-8a59-7278083916c9]
30+
description = "example knapsack"
31+
32+
[f23a2449-d67c-4c26-bf3e-cde020f27ecc]
33+
description = "8 items"
34+
35+
[7c682ae9-c385-4241-a197-d2fa02c81a11]
36+
description = "15 items"
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
=begin
2+
Write your code for the 'Knapsack' exercise in this file. Make the tests in
3+
`knapsack_test.rb` pass.
4+
5+
To get started with TDD, see the `README.md` file in your
6+
`ruby/knapsack` directory.
7+
=end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
require 'minitest/autorun'
2+
require_relative 'knapsack'
3+
4+
class KnapsackTest < Minitest::Test
5+
Item = Data.define(:weight, :value)
6+
7+
def test_no_items
8+
# skip
9+
max_weight = 100
10+
items = []
11+
expected = 0
12+
actual = Knapsack.new(max_weight).max_value(items)
13+
14+
assert_equal expected, actual,
15+
"When there are no items, the resulting value must be 0."
16+
end
17+
18+
def test_one_item_too_heavy
19+
skip
20+
max_weight = 10
21+
items = [Item.new(weight: 100, value: 1)]
22+
expected = 0
23+
actual = Knapsack.new(max_weight).max_value(items)
24+
25+
assert_equal expected, actual,
26+
"When there is one item that is too heavy, the resulting value must be 0."
27+
end
28+
29+
def test_five_items_cannot_be_greedy_by_weight
30+
skip
31+
max_weight = 10
32+
items = [
33+
Item.new(weight: 2, value: 5),
34+
Item.new(weight: 2, value: 5),
35+
Item.new(weight: 2, value: 5),
36+
Item.new(weight: 2, value: 5),
37+
Item.new(weight: 10, value: 21)
38+
]
39+
expected = 21
40+
actual = Knapsack.new(max_weight).max_value(items)
41+
42+
assert_equal expected, actual,
43+
"Do not prioritize the most valuable items per weight when that would " \
44+
"result in a lower total value."
45+
end
46+
47+
def test_five_items_cannot_be_greedy_by_value
48+
skip
49+
max_weight = 10
50+
items = [
51+
Item.new(weight: 2, value: 20),
52+
Item.new(weight: 2, value: 20),
53+
Item.new(weight: 2, value: 20),
54+
Item.new(weight: 2, value: 20),
55+
Item.new(weight: 10, value: 50)
56+
]
57+
expected = 80
58+
actual = Knapsack.new(max_weight).max_value(items)
59+
60+
assert_equal expected, actual,
61+
"Do not prioritize the items with the highest value when that would " \
62+
"result in a lower total value."
63+
end
64+
65+
def test_example_knapsack
66+
skip
67+
max_weight = 10
68+
items = [
69+
Item.new(weight: 5, value: 10),
70+
Item.new(weight: 4, value: 40),
71+
Item.new(weight: 6, value: 30),
72+
Item.new(weight: 4, value: 50)
73+
]
74+
expected = 90
75+
actual = Knapsack.new(max_weight).max_value(items)
76+
77+
assert_equal expected, actual,
78+
"A small example knapsack must result in a value of 90."
79+
end
80+
81+
def test_eight_items
82+
skip
83+
max_weight = 104
84+
items = [
85+
Item.new(weight: 25, value: 350),
86+
Item.new(weight: 35, value: 400),
87+
Item.new(weight: 45, value: 450),
88+
Item.new(weight: 5, value: 20),
89+
Item.new(weight: 25, value: 70),
90+
Item.new(weight: 3, value: 8),
91+
Item.new(weight: 2, value: 5),
92+
Item.new(weight: 2, value: 5)
93+
]
94+
expected = 900
95+
actual = Knapsack.new(max_weight).max_value(items)
96+
97+
assert_equal expected, actual,
98+
"A larger example knapsack with 8 items must result in a value of 900."
99+
end
100+
101+
def test_fifteen_items
102+
skip
103+
max_weight = 750
104+
items = [
105+
Item.new(weight: 70, value: 135),
106+
Item.new(weight: 73, value: 139),
107+
Item.new(weight: 77, value: 149),
108+
Item.new(weight: 80, value: 150),
109+
Item.new(weight: 82, value: 156),
110+
Item.new(weight: 87, value: 163),
111+
Item.new(weight: 90, value: 173),
112+
Item.new(weight: 94, value: 184),
113+
Item.new(weight: 98, value: 192),
114+
Item.new(weight: 106, value: 201),
115+
Item.new(weight: 110, value: 210),
116+
Item.new(weight: 113, value: 214),
117+
Item.new(weight: 115, value: 221),
118+
Item.new(weight: 118, value: 229),
119+
Item.new(weight: 120, value: 240)
120+
]
121+
expected = 1458
122+
actual = Knapsack.new(max_weight).max_value(items)
123+
124+
assert_equal expected, actual,
125+
"A very large example knapsack with 15 items must result in a value of 1458."
126+
end
127+
end

0 commit comments

Comments
 (0)