使用ga算法解决背包问题
I’m a nomad and live out of one carry-on bag. This means that the total weight of all my worldly possessions must fall under airline cabin baggage weight limits — usually 10kg. On some smaller airlines, however, this weight limit drops to 7kg. Occasionally, I have to decide not to bring something with me to adjust to the smaller weight limit.
我是一个Nomad民族,只能靠一个手提包生活。 这意味着我所有世俗财产的总重量必须在机舱行李重量限制之内(通常为10公斤)。 但是,在一些较小的航空公司上,此重量限制降至7kg。 有时,我必须决定不带东西以适应较小的体重限制。
As a practical exercise, deciding what to leave behind (or get rid of altogether) entails laying out all my things and choosing which ones to keep. That decision is based on the item’s usefulness to me (its worth) and its weight.
作为一项实践练习,决定留下什么(或完全摆脱掉)需要布置我所有的东西并选择要保留的东西。 该决定基于该商品对我的有用性(价值)和重量。
Being a programmer, I’m aware that decisions like this could be made more efficiently by a computer. It’s done so frequently and so ubiquitously, in fact, that many will recognize this scenario as the classic packing problem or knapsack problem. How do I go about telling a computer to put as many important items in my bag as possible while coming in at or under a weight limit of 7kg? With algorithms! Yay!
作为程序员,我知道可以通过计算机更有效地做出这样的决定。 实际上,这样做是如此频繁且无处不在,以至于许多人都将这种情况视为经典的打包问题或背包问题。 在告诉我7公斤或以下的重量时,如何让计算机将尽可能多的重要物品放入包中? 有了算法! 好极了!
I’ll discuss two common approaches to solving the knapsack problem: one called a greedy algorithm, and another called dynamic programming (a little harder, but better, faster, stronger…).
我将讨论两种解决背包问题的常用方法:一种称为贪婪算法 ,另一种称为动态编程 (难度更高,但更好,更快,更强……)。
Let’s get to it.
让我们开始吧。
设置 (The set up)
I prepared my data in the form of a CSV file with three columns: the item’s name (a string), a representation of its worth (an integer), and its weight in grams (an integer). There are 40 items in total. I represented worth by ranking each item from 40 to 1, with 40 being the most important and 1 equating with something like “why do I even have this again?” (If you’ve never listed out all your possessions and ranked them by order of how useful they are to you, I highly recommend you try it. It can be a very revealing exercise.)
我以CSV文件的形式准备了数据,该文件包括三列:商品名称(字符串),商品价值(整数)和重量(克)(整数)的表示形式。 总共有40个项目。 我通过将每一项从40排名为1来表示价值,其中40是最重要的,而1则等同于“为什么我还要再次拥有它?” (如果您从未列出所有财产,并按对您有多有用的顺序对其进行了排名,我强烈建议您尝试一下。这可能是一个非常有意义的练习。)
Total weight of all items and bag: 9003g
所有物品和袋子的总重量: 9003g
Bag weight: 1415g
包重: 1415g
Airline limit: 7000g
航空公司限制: 7000g
Maximum weight of items I can pack: 5585g
我可以包装的最大物品重量: 5585g
Total possible worth of items: 820
项目的总可能价值: 820
The challenge: Pack as many items as the limit allows while maximizing the total worth.
挑战:在最大限度地增加总价值的同时,包装尽可能多的物品。
数据结构 (Data structures)
读取文件 (Reading in a file)
Before we can begin thinking about how to solve the knapsack problem, we have to solve the problem of reading in and storing our data. Thankfully, the Go standard library’s io/ioutil
package makes the first part straightforward.
在开始考虑如何解决背包问题之前,我们必须解决读取和存储数据的问题。 幸运的是,Go标准库的io/ioutil
软件包使第一部分变得简单。
package main
import (
"fmt"
"io/ioutil"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func readItems(path string) {
dat, err := ioutil.ReadFile(path)
check(err)
fmt.Print(string(dat))
}
The ReadFile()
function takes a file path and returns the file’s contents and an error (nil
if the call is successful) so we’ve also created a check()
function to handle any errors that might be returned. In a real-world application, we probably would want to do something more sophisticated than panic
, but that’s not important right now.
ReadFile()
函数采用文件路径并返回文件内容和错误(如果调用成功,则返回nil
),因此我们还创建了一个check()
函数来处理可能返回的任何错误。 在现实世界中的应用程序中,我们可能想做一些比panic
更复杂的事情,但是现在这并不重要。
创建一个结构 (Creating a struct)
Now that we’ve got our data, we should probably do something with it. Since we’re working with real-life items and a real-life bag, let’s create some types to represent them and make it easier to conceptualize our program. A struct
in Go is a typed collection of fields. Here are our two types:
现在我们已经有了数据,我们可能应该对它进行一些处理。 由于我们正在处理现实生活中的物品和生活中的包,因此让我们创建一些类型来表示它们,并使概念化我们的程序更加容易。 Go中的struct
是字段的类型化集合。 这是我们的两种类型:
type item struct {
name string
worth, weight int
}
type bag struct {
bagWeight, currItemsWeight, maxItemsWeight, totalWeight int
items []item
}
It is helpful to use field names that are very descriptive. You can see that the structs are set up just as we’ve described the things they represent. An item
has a name
(string), and a worth
and weight
(integers). A bag
has several fields of type int
representing its attributes, and also has the ability to hold items
, represented in the struct as a slice of item
type thingamabobbers.
使用描述性很强的字段名称会有所帮助。 您可以看到这些结构已经建立,就像我们描述它们所代表的事物一样。 item
具有name
(字符串), worth
和weight
(整数)。 bag
具有表示其属性的int
类型的几个字段,还具有容纳items
的能力,该对象在struct中表示为item
类型的东西thingamabobbers。
解析和存储我们的数据 (Parsing and storing our data)
Several comprehensive Go packages exist that we could use to parse our CSV data… but where’s the fun in that? Let’s go basic with some string splitting and a for loop. Here’s our updated readItems()
function:
我们可以使用几个全面的Go包来解析CSV数据……但是,这样做的乐趣何在? 让我们开始一些字符串拆分和for循环的基本操作。 这是我们更新的readItems()
函数:
func readItems(path string) []item {
dat, err := ioutil.ReadFile(path)
check(err)
lines := strings.Split(string(dat), "\n")
itemList := make([]item, 0)
for i, v := range lines {
if i == 0 {
continue
}
s := strings.Split(v, ",")
newItemWorth, _ := strconv.Atoi(s[1])
newItemWeight, _ := strconv.Atoi(s[2])
newItem := item{name: s[0], worth: newItemWorth, weight: newItemWeight}
itemList = append(itemList, newItem)
}
return itemList
}
Using strings.Split
, we split our dat
on newlines. We then create an empty itemList
to hold our items.
使用strings.Split
,我们将dat
换行。 然后,我们创建一个空的itemList
来保存我们的项目。
In our for loop, we skip the first line of our CSV file (the headers) then iterate over each line. We use strconv.Atoi
(read “A to i”) to convert the values for each item’s worth and weight into integers. We then create a newItem
with these field values and append it to the i