diff --git a/config.json b/config.json index bbf538156..abea8d081 100644 --- a/config.json +++ b/config.json @@ -1812,6 +1812,18 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "knapsack", + "name": "Knapsack", + "uuid": "eae2fd23-c484-4dc6-b8fe-1fa055bab8ba", + "practices": [], + "prerequisites": [], + "difficulty": 6, + "topics": [ + "algorithms", + "dynamic_programming" + ] + }, { "slug": "dominoes", "name": "Dominoes", diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md new file mode 100644 index 000000000..3411db988 --- /dev/null +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -0,0 +1,25 @@ +# Instructions + +Your task is to determine which items to take so that the total value of his selection is maximized, taking into account the knapsack's carrying capacity. + +Items will be represented as a list of items. +Each item will have a weight and value. +All values given will be strictly positive. +Bob can take only one of each item. + +For example: + +```text +Items: [ + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } +] + +Knapsack Maximum Weight: 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. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 000000000..9b2bed8b4 --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Bob is a thief. +After months of careful planning, he finally manages to crack the security systems of a fancy store. + +In front of him are many items, each with a value and weight. +Bob would gladly take all of the items, but his knapsack can only hold so much weight. +Bob has to carefully consider which items to take so that the total value of his selection is maximized. diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json new file mode 100644 index 000000000..467a88cc9 --- /dev/null +++ b/exercises/practice/knapsack/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": ["jesse-kroon"], + "files": { + "solution": [ + "knapsack.go" + ], + "test": [ + "knapsack_test.go" + ], + "editor": [ + "cases_test.go" + ], + "example": [ + ".meta/example.go" + ] + }, + "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/exercises/practice/knapsack/.meta/example.go b/exercises/practice/knapsack/.meta/example.go new file mode 100644 index 000000000..dcc16874e --- /dev/null +++ b/exercises/practice/knapsack/.meta/example.go @@ -0,0 +1,28 @@ +package knapsack + +import "math" + +type Item struct { + Weight, Value int +} + +func Knapsack(maximumWeight int, items []Item) int { + amountOfItems := len(items) + knapsack := make([][]int, amountOfItems+1) + + for i := range knapsack { + knapsack[i] = make([]int, maximumWeight+1) + } + + for currentItem := 1; currentItem <= amountOfItems; currentItem++ { + for currentItemWeight := 0; currentItemWeight <= maximumWeight; currentItemWeight++ { + if items[currentItem-1].Weight > currentItemWeight { + knapsack[currentItem][currentItemWeight] = knapsack[currentItem-1][currentItemWeight] + } else { + knapsack[currentItem][currentItemWeight] = int(math.Max(float64(knapsack[currentItem-1][currentItemWeight]), float64(items[currentItem-1].Value+knapsack[currentItem-1][currentItemWeight-items[currentItem-1].Weight]))) + } + } + } + + return knapsack[amountOfItems][maximumWeight] +} diff --git a/exercises/practice/knapsack/.meta/gen.go b/exercises/practice/knapsack/.meta/gen.go new file mode 100644 index 000000000..bb746e1bb --- /dev/null +++ b/exercises/practice/knapsack/.meta/gen.go @@ -0,0 +1,72 @@ +package main + +import ( + "log" + "text/template" + + "../../../../gen" +) + +type item struct { + Weight int `json:"weight"` + Value int `json:"value"` +} +type maximumValueCaseInput struct { + MaximumWeight int `json:"maximumWeight"` + Items []item `json:"items"` +} + +type maximumValueCase struct { + Description string `json:"description"` + Input maximumValueCaseInput `json:"input"` + Expected int `json:"expected"` +} + +func main() { + t, err := template.New("").Parse(tmpl) + if err != nil { + log.Fatal(err) + } + var j = map[string]interface{}{ + "maximumValue": &[]maximumValueCase{}, + } + if err := gen.Gen("knapsack", j, t); err != nil { + log.Fatal(err) + } +} + +var tmpl = `package knapsack + +{{.Header}} + +type maximumValueCaseInput struct { + MaximumWeight int + Items []Item +} + +type maximumValueTest struct { + description string + input maximumValueCaseInput + expected int +} + +var maximumValueTests = []maximumValueTest { + {{range .J.maximumValue}} + { + description: {{printf "%q" .Description}}, + input: maximumValueCaseInput { + MaximumWeight: {{printf "%d" .Input.MaximumWeight}}, + Items: []Item { + {{range .Input.Items}} + { + Weight: {{printf "%d" .Weight}}, + Value: {{printf "%d" .Value}}, + }, + {{end}} + }, + }, + expected: {{printf "%d" .Expected}}, + }, + {{end}} +} +` diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml new file mode 100644 index 000000000..8e013ef19 --- /dev/null +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -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" diff --git a/exercises/practice/knapsack/cases_test.go b/exercises/practice/knapsack/cases_test.go new file mode 100644 index 000000000..ee6fc80ad --- /dev/null +++ b/exercises/practice/knapsack/cases_test.go @@ -0,0 +1,279 @@ +package knapsack + +// This is an auto-generated file. Do not change it manually. Run the generator to update the file. +// See https://github.com/exercism/go#synchronizing-tests-and-instructions +// Source: exercism/problem-specifications +// Commit: 6c1d8e2 Knapsack: Fix empty list of items (#2350) + +type maximumValueCaseInput struct { + MaximumWeight int + Items []Item +} + +type maximumValueTest struct { + description string + input maximumValueCaseInput + expected int +} + +var maximumValueTests = []maximumValueTest{ + + { + description: "no items", + input: maximumValueCaseInput{ + MaximumWeight: 100, + Items: []Item{}, + }, + expected: 0, + }, + + { + description: "one item, too heavy", + input: maximumValueCaseInput{ + MaximumWeight: 10, + Items: []Item{ + + { + Weight: 100, + Value: 1, + }, + }, + }, + expected: 0, + }, + + { + description: "five items (cannot be greedy by weight)", + input: maximumValueCaseInput{ + MaximumWeight: 10, + Items: []Item{ + + { + Weight: 2, + Value: 5, + }, + + { + Weight: 2, + Value: 5, + }, + + { + Weight: 2, + Value: 5, + }, + + { + Weight: 2, + Value: 5, + }, + + { + Weight: 10, + Value: 21, + }, + }, + }, + expected: 21, + }, + + { + description: "five items (cannot be greedy by value)", + input: maximumValueCaseInput{ + MaximumWeight: 10, + Items: []Item{ + + { + Weight: 2, + Value: 20, + }, + + { + Weight: 2, + Value: 20, + }, + + { + Weight: 2, + Value: 20, + }, + + { + Weight: 2, + Value: 20, + }, + + { + Weight: 10, + Value: 50, + }, + }, + }, + expected: 80, + }, + + { + description: "example knapsack", + input: maximumValueCaseInput{ + MaximumWeight: 10, + Items: []Item{ + + { + Weight: 5, + Value: 10, + }, + + { + Weight: 4, + Value: 40, + }, + + { + Weight: 6, + Value: 30, + }, + + { + Weight: 4, + Value: 50, + }, + }, + }, + expected: 90, + }, + + { + description: "8 items", + input: maximumValueCaseInput{ + MaximumWeight: 104, + Items: []Item{ + + { + 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, + }, + }, + }, + expected: 900, + }, + + { + description: "15 items", + input: maximumValueCaseInput{ + MaximumWeight: 750, + Items: []Item{ + + { + 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, + }, + }, + }, + expected: 1458, + }, +} diff --git a/exercises/practice/knapsack/go.mod b/exercises/practice/knapsack/go.mod new file mode 100644 index 000000000..b6d8a9318 --- /dev/null +++ b/exercises/practice/knapsack/go.mod @@ -0,0 +1,3 @@ +module knapsack + +go 1.18 diff --git a/exercises/practice/knapsack/knapsack.go b/exercises/practice/knapsack/knapsack.go new file mode 100644 index 000000000..dc5dabb76 --- /dev/null +++ b/exercises/practice/knapsack/knapsack.go @@ -0,0 +1,12 @@ +package knapsack + +type Item struct { + Weight, Value int +} + +// Knapsack takes in a maximum carrying capacity and a collection of items +// and returns the maximum value that can be carried by the knapsack +// given that the knapsack can only carry a maximum weight given by maximumWeight +func Knapsack(maximumWeight int, items []Item) int { + panic("Please implement the Knapsack() function") +} diff --git a/exercises/practice/knapsack/knapsack_test.go b/exercises/practice/knapsack/knapsack_test.go new file mode 100644 index 000000000..ca1a47744 --- /dev/null +++ b/exercises/practice/knapsack/knapsack_test.go @@ -0,0 +1,22 @@ +package knapsack + +import "testing" + +func TestKnapsack(t *testing.T) { + for _, tc := range maximumValueTests { + actual := Knapsack(tc.input.MaximumWeight, tc.input.Items) + expected := tc.expected + + if actual != expected { + t.Fatalf("Knapsack(%d, %+v) = %d, want %d", tc.input.MaximumWeight, tc.input.Items, actual, tc.expected) + } + } +} + +func BenchmarkKnapsack(b *testing.B) { + for i := 0; i < b.N; i++ { + for _, tc := range maximumValueTests { + Knapsack(tc.input.MaximumWeight, tc.input.Items) + } + } +}