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

Add exercise knapsack #563

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,14 @@
"events",
"reactive_programming"
]
},
{
"slug": "knapsack",
"name": "Knapsack",
"uuid": "6cd9b4d2-2274-4b56-98cf-1c820f1da836",
"practices": [],
"prerequisites": [],
"difficulty": 4
}
]
},
Expand Down
35 changes: 35 additions & 0 deletions exercises/practice/knapsack/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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 items.
Each item will have a weight and value.

For example:

```none
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.
19 changes: 19 additions & 0 deletions exercises/practice/knapsack/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"siebenschlaefer"
],
"files": {
"solution": [
"knapsack.nim"
],
"test": [
"test_knapsack.nim"
],
"example": [
".meta/example.nim"
]
},
"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"
}
8 changes: 8 additions & 0 deletions exercises/practice/knapsack/.meta/example.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type Item = tuple[weight: int, value: int]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type Item = tuple[weight: int, value: int]
type Item* = object
weight*: int
value*: int

I think we should go with an object here, although it'll require some other changes.

To reduce the verbosity this would otherwise produce in the test file, I think I'd suggest adding a func there that takes an openArray[(int, int)] and returns a seq of Item. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not against this change, but as an inexperienced Nim programmer I want to learn:
Why do you prefer an object over a tuple in this case?

As for the verbosity: I don't think it's too bad currently but I'm fine with all the alternatives.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For myself, the main reason to avoid tuples is because tuples are duck-typed. Consider the following example:

type
  a = tuple
    a: int
    b: int
  b = tuple
    a: int
    b: int
 
const
  c: a = (a: 1, b: 2)
  d: b = c

This would compile because a tuple just means a specific ordering of elements. The names of the field (or even if they have names) and the name of the type is not part of the type of the tuple.

The equivalent code for objects, however, would not compile because, even if they do have the same field names and types, because they are 2 different and separate definitions.

This can lead to confusion.

Consider a tuple representing an RGB color and a point in 3-D space. They are interchangeable with each other but don't represent equal values. An object, however, is not interchangeable as such and is therefore not subject to being mistakenly converted

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, tuples can be problematic if they get constructed without field names or accessed with an index: With (12, 34) it's not immediately obvious which of the two is the weight and which the value, and item[1] doesn't tell the reader that it accesses the value.

But otherwise, frankly, I'm not convinced: The two tuples in your example can only be assigned to each other because they have the same number of fields, with the same types and names.
If I have an RGB color tuple[r, g, b: int] and a 3D point tuple[x, y, z: int] they are not interchangeable because their fields have different names.
And wouldn't that argument apply to all uses of tuples, anywhere?

But I'm OK with making them objects if you think that's better. The only downside would be that the tests would need the typename for each construction of an Item.
Creating a helper function would allow us to shorten that but wouldn't we bring back the ambiguity in return?


proc maximumValue*(maximumWeight: int, items: openArray[Item]): int =
var dp = newSeq[int](maximumWeight + 1)
for item in items:
for weight in countdown(maximumWeight, item.weight):
dp[weight] = max(dp[weight], item.value + dp[weight - item.weight])
result = dp[maximumWeight]
36 changes: 36 additions & 0 deletions exercises/practice/knapsack/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[a4d7d2f0-ad8a-460c-86f3-88ba709d41a7]
description = "no items"
include = false

[3993a824-c20e-493d-b3c9-ee8a7753ee59]
description = "no items"
reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7"

[1d39e98c-6249-4a8b-912f-87cb12e506b0]
description = "one item, too heavy"

[833ea310-6323-44f2-9d27-a278740ffbd8]
description = "five items (cannot be greedy by weight)"

[277cdc52-f835-4c7d-872b-bff17bab2456]
description = "five items (cannot be greedy by value)"

[81d8e679-442b-4f7a-8a59-7278083916c9]
description = "example knapsack"

[f23a2449-d67c-4c26-bf3e-cde020f27ecc]
description = "8 items"

[7c682ae9-c385-4241-a197-d2fa02c81a11]
description = "15 items"
4 changes: 4 additions & 0 deletions exercises/practice/knapsack/knapsack.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type Item = tuple[weight: int, value: int]

proc maximumValue*(maximumWeight: int, items: openArray[Item]): int =
discard
64 changes: 64 additions & 0 deletions exercises/practice/knapsack/test_knapsack.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import unittest
import knapsack

suite "knapsack":
test "no items":
const maximumWeight = 100
const items: array[0, tuple[weight: int, value: int]] = []
const expected = 0
check maximumValue(maximumWeight, items) == expected

test "one item, too heavy":
const maximumWeight = 10
const items = [(weight: 100, value: 1)]
const expected = 0
check maximumValue(maximumWeight, items) == expected

test "five items (cannot be greedy by weight)":
const maximumWeight = 10
const items = [
(weight: 2, value: 5), (weight: 2, value: 5), (weight: 2, value: 5),
(weight: 2, value: 5), (weight: 10, value: 21)
]
const expected = 21
check maximumValue(maximumWeight, items) == expected

test "five items (cannot be greedy by value)":
const maximumWeight = 10
const items = [
(weight: 2, value: 20), (weight: 2, value: 20), (weight: 2, value: 20),
(weight: 2, value: 20), (weight: 10, value: 50)
]
const expected = 80
check maximumValue(maximumWeight, items) == expected

test "example knapsack":
const maximumWeight = 10
const items = [
(weight: 5, value: 10), (weight: 4, value: 40), (weight: 6, value: 30),
(weight: 4, value: 50)
]
const expected = 90
check maximumValue(maximumWeight, items) == expected

test "8 items":
const maximumWeight = 104
const items = [
(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)
]
const expected = 900
check maximumValue(maximumWeight, items) == expected

test "15 items":
const maximumWeight = 750
const items = [
(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)
]
const expected = 1458
check maximumValue(maximumWeight, items) == expected
Loading