【问题标题】:Bin packing implementation in C++ with STL使用 STL 在 C++ 中的 bin 打包实现
【发布时间】:2010-07-11 03:45:56
【问题描述】:

这是我第一次使用这个网站,很抱歉有任何格式错误或奇怪的表述,我会尽力遵守这个网站的规则,但我可能会在开始时犯一些错误。

我现在正在使用 STL 容器在 C++ 中实现一些不同的装箱算法。在当前代码中,我仍然有一些逻辑错误需要修复,但这个问题更多的是关于程序的结构。关于如何构建程序以尽量减少逻辑错误的数量并使其尽可能易于阅读,我不希望有第二意见。在目前的状态下,我只是觉得这不是最好的方法,但我现在真的没有看到任何其他方法来编写我的代码。

问题是一个动态在线装箱问题。从某种意义上说,它是动态的,即物品在离开被分配到的垃圾箱之前有任意时间。

简而言之,我的问题是:
Bin 打包算法的结构在 C++ 中的外观如何?
STL 容器是使实现能够处理任意长度输入的好工具吗?
我应该如何以一种良好、易于阅读和实施的方式处理容器?

关于我自己的代码的一些想法:
使用类在处理不同箱的列表和这些箱中的项目列表之间做出很好的区分。
使实施尽可能有效。
易于运行许多不同的数据长度和用于基准测试的文件。

#include <iostream>
#include <fstream>
#include <list>
#include <queue>
#include <string>
#include <vector>

using namespace std;

struct type_item {
    int size;
    int life;
    bool operator < (const type_item& input)
    {
        return size < input.size;
    }
};

class Class_bin {
    double load;
    list<type_item> contents;
    list<type_item>::iterator i;
public:
    Class_bin ();
    bool operator < (Class_bin);
    bool full (type_item);
    void push_bin (type_item);
    double check_load ();
    void check_dead ();
    void print_bin ();
};

Class_bin::Class_bin () {
    load=0.0;
}

bool Class_bin::operator < (Class_bin input){
    return load < input.load;
}

bool Class_bin::full (type_item input) {
    if (load+(1.0/(double) input.size)>1) {
        return false;
    }
    else {
        return true;
    }
}

void Class_bin::push_bin (type_item input) {
    int sum=0;

    contents.push_back(input);
    for (i=contents.begin(); i!=contents.end(); ++i) {
        sum+=i->size;
    }
    load+=1.0/(double) sum;
}

double Class_bin::check_load () {
    return load;
}

void Class_bin::check_dead () {
    for (i=contents.begin(); i!=contents.end(); ++i) {
        i->life--;
        if (i->life==0) {
            contents.erase(i);
        }
    }
}

void Class_bin::print_bin () {
    for (i=contents.begin (); i!=contents.end (); ++i) {
        cout << i->size << "  ";
    }
}


class Class_list_of_bins {
    list<Class_bin> list_of_bins;
    list<Class_bin>::iterator i;
public:

    void push_list (type_item);
    void sort_list ();
    void check_dead ();
    void print_list ();
private:
    Class_bin new_bin (type_item);
    bool comparator (type_item, type_item);
};

Class_bin Class_list_of_bins::new_bin (type_item input) {
    Class_bin temp;

    temp.push_bin (input);

    return temp;
}

void Class_list_of_bins::push_list (type_item input) {
    if (list_of_bins.empty ()) {
        list_of_bins.push_front (new_bin(input));
        return;
    }
    for (i=list_of_bins.begin (); i!=list_of_bins.end (); ++i) {
        if (!i->full (input)) {
            i->push_bin (input);
            return;
        }
    }
    list_of_bins.push_front (new_bin(input));
}

void Class_list_of_bins::sort_list () {
    list_of_bins.sort();
}

void Class_list_of_bins::check_dead () {
    for (i=list_of_bins.begin (); i !=list_of_bins.end (); ++i) {
        i->check_dead ();
    }
}

void Class_list_of_bins::print_list () {
    for (i=list_of_bins.begin (); i!=list_of_bins.end (); ++i) {
        i->print_bin ();
        cout << "\n";
    }
}


int main () {
    int i, number_of_items;

    type_item buffer;
    Class_list_of_bins bins;

    queue<type_item> input;

    string filename;
    fstream file;


    cout << "Input file name: ";
    cin >> filename;
    cout << endl;

    file.open (filename.c_str(), ios::in);

    file >> number_of_items;

    for (i=0; i<number_of_items; ++i) {
        file >> buffer.size;
        file >> buffer.life;
        input.push (buffer);
    }

    file.close ();

    while (!input.empty ()) {
        buffer=input.front ();
        input.pop ();
        bins.push_list (buffer);
    }

    bins.print_list ();

    return 0;
}

请注意,这只是我的代码的快照,尚未正常运行

不想用不相关的闲聊把这件事弄得一团糟,只想感谢做出贡献的人,我会检查我的代码并希望能够更好地构建我的编程结构

【问题讨论】:

    标签: c++ stl bin-packing


    【解决方案1】:

    Bin 打包算法的结构在 C++ 中是怎样的?

    嗯,理想情况下,您应该有几种装箱算法,它们被分成不同的函数,它们的区别仅在于算法的逻辑。该算法应该在很大程度上独立于您的数据表示,因此您只需一个函数调用即可更改您的算法。

    您可以查看STL Algorithms 的共同点。主要是,它们在迭代器而不是容器上运行,但正如我在下面详述的那样,我最初不建议你这样做。您应该了解可用的算法并在您的实现中利用它们。

    STL 容器是使实现能够处理任意长度的输入的好工具吗?

    它通常是这样工作的:创建一个容器,填充容器,将算法应用于容器。

    从你的需求描述来看,这就是你将如何使用它,所以我认为它会很好。您的装箱算法与大多数 STL 算法之间有一个重要区别。

    STL 算法要么不修改,要么将元素插入到目标。另一方面,bin-packing 是“这是一个垃圾箱列表,使用它们或添加一个新垃圾箱”。用迭代器做到这一点并非不可能,但可能不值得付出努力。我首先对容器进行操作,得到一个工作程序,备份它,然后看看你是否可以让它只对迭代器工作。

    我应该如何以一种好的、易于阅读和实施的方式处理容器?

    我会采用这种方法,描述您的输入和输出:

    • 输入:项目集合,任意长度,任意顺序。
    • 输出:由算法确定的 bin 集合。每个 bin 都包含一组项目。

    然后我会担心“我的算法需要做什么?”

    • 经常检查垃圾箱是否“这件物品适合吗?”
    • 您的Class_bin 很好地封装了所需内容。
    • 避免将代码与“print()”等不相关的内容混在一起 - 使用非成员帮助函数。

    type_item

    struct type_item {
        int size;
        int life;
        bool operator < (const type_item& input)
        {
            return size < input.size;
        }
    };
    

    目前还不清楚life(或死亡)的用途。我无法想象这个概念与实现装箱算法有关。也许它应该被排除在外?

    这是个人喜好,但我不喜欢将operator&lt; 分配给我的对象。对象通常是不平凡的,并且有许多小于的含义。例如,一种算法可能希望所有活动项都排在死项之前。为了清楚起见,我通常将其包装在另一个结构中:

    struct type_item {
        int size;
        int life;
        struct SizeIsLess {
          // Note this becomes a function object, which makes it easy to use with
          // STL algorithms.
          bool operator() (const type_item& lhs, const type_item& rhs)
          {
              return lhs.size < rhs.size;
          }
        }
    };
    
    vector<type_item> items;
    std::sort(items.begin, items.end(), type_item::SizeIsLess);
    

    类_bin

    class Class_bin {
        double load;
        list<type_item> contents;
        list<type_item>::iterator i;
    public:
        Class_bin ();
        bool operator < (Class_bin);
        bool full (type_item);
        void push_bin (type_item);
        double check_load ();
        void check_dead ();
        void print_bin ();
    };
    
    • 我会跳过所有类型上的 Class_ 前缀 - 这有点过分,应该从代码中清楚地看到。 (这是hungarian notation 的变体。程序员往往对它怀有敌意。)
    • 你不应该有一个类成员i(迭代器)。它不是类状态的一部分。如果您在所有成员中都需要它,那没关系,只需在此处重新声明即可。如果输入太长,请使用typedef
    • 很难量化“bin1 小于 bin2”,所以我建议删除 operator&lt;
    • bool full(type_item) 有点误导。我可能会使用bool can_hold(type_item)。对我来说,bool full() 会在剩余 空间时返回 true。
    • check_load() 似乎更清楚地命名为 load()
    • 同样,尚不清楚check_dead() 应该完成什么。
    • 我认为您可以删除 print_bin 并将其写为非成员函数,以保持您的对象更清洁。
    • StackOverflow 上的某些人会向我开枪,但我会考虑将其设为结构,并将负载和项目列表公开。您似乎不太关心这里的封装(您只需要创建这个对象,因此您不需要每次都重新计算负载)。

    Class_list_of_bins

    class Class_list_of_bins {
        list<Class_bin> list_of_bins;
        list<Class_bin>::iterator i;
    public:
    
        void push_list (type_item);
        void sort_list ();
        void check_dead ();
        void print_list ();
    private:
        Class_bin new_bin (type_item);
        bool comparator (type_item, type_item);
    };
    
    • 我认为你完全可以不用这个课程。
    • 从概念上讲,它代表一个容器,所以只需使用 STL 容器即可。您可以将这些方法实现为非成员函数。请注意,sort_list 可以替换为 std::sort
    • comparator 这个名字太笼统了,它没有说明它比较什么或为什么,所以考虑更清楚。

    总体评价

    总的来说,我认为您选择的类充分模拟了您要表示的空间,所以您会没事的。

    我可能会这样构建我的项目:

    struct bin {
      double load;  // sum of item sizes.
      std::list<type_item> items;
    
      bin() : load(0) { }
    };
    
    // Returns true if the bin can fit the item passed to the constructor.
    struct bin_can_fit {
      bin_can_fit(type_item &item) : item_(item) { }
      bool operator()(const bin &b) {
        return item_.size < b.free_space;
      }
     private:
      type_item item_;
    };
    
    // ItemIter is an iterator over the items.
    // BinOutputIter is an output iterator we can use to put bins.
    template <ItemIter, BinOutputIter>
    void bin_pack_first_fit(ItemIter curr, ItemIter end, BinOutputIter output_bins) {
      std::vector<bin> bins;  // Create a local bin container, to simplify life.
      for (; curr != end; ++curr) {
        // Use a helper predicate to check whether the bin can fit this item.
        // This is untested, but just for an idea.
        std::vector<bin>::iterator bin_it =
            std::find_if(bins.begin(), bins.end(), bin_can_fit(*curr));
        if (bin_it == bins.end()) {
          // Did not find a bin with enough space, add a new bin.
          bins.push_back(bin);
          // push_back invalidates iterators, so reassign bin_it to the last item.
          bin_it = std::advance(bins.begin(), bins.size() - 1);
        }
    
        // bin_it now points to the bin to put the item in.
        bin_it->items.push_back(*curr);
        bin_it->load += curr.size();
      }
      std::copy(bins.begin(), bins.end(), output_bins);  // Apply our bins to the destination.
    }
    
    void main(int argc, char** argv) {
      std::vector<type_item> items;
      // ... fill items
      std::vector<bin> bins;
      bin_pack_first_fit(items.begin(), items.end(), std::back_inserter(bins));
    }
    

    【讨论】:

      【解决方案2】:

      一些想法:

      你的名字有些地方乱七八糟。

      1. 你有很多名为input的参数,那是没有意义的
      2. 我希望 full() 检查它是否已满,而不是它是否适合其他东西
      3. 我认为 push_bin 不会推送垃圾箱
      4. check_dead 修改对象(我希望有一个名为 check_* 的东西,只是告诉我有关该对象的一些信息)
      5. 不要在类和类型的名称中输入 Class 和 type 之类的内容。
      6. class_list_of_bins 似乎描述的是里面的东西,而不是对象是什么。
      7. push_list 不推送列表
      8. 不要将 _list 之类的东西附加到列表类中的每个方法,如果它是一个列表对象,我们已经知道它是一个列表方法

      考虑到生活和负载的参数,我对你在做什么感到困惑。我熟悉的装箱问题只有尺寸。我猜加班时有些物品会从垃圾箱中取出并因此消失?

      关于你的课程的一些进一步的想法

      Class_list_of_bins 将太多的自身暴露给外界。为什么外界要check_dead或者sort_list呢?那是没有人的事,但对象本身。你应该在那个类上拥有的公共方法真的应该是这样的 * 添加一个项目到垃圾箱的集合 * 打印解决方案 * 迈出未来的一步

      list<Class_bin>::iterator i;
      

      糟糕,糟糕,糟糕!除非它们实际上是成员国,否则不要将成员变量放在您的变量上。您应该在使用它的地方定义该迭代器。如果你想节省一些输入,添加这个: typedef list::iterator bin_iterator 然后你使用 bin_iterator 作为类型。

      扩展答案

      这是我的伪代码:

      class Item
      {
           Item(Istream & input)
           {
               read input description of item
           }
      
           double size_needed() { return actual size required (out of 1) for this item)
           bool alive() { return true if object is still alive}
           void do_timestep() { decrement life }
           void print() { print something }
      }
      
      class Bin
      {
          vector of Items
          double remaining_space
      
      
          bool can_add(Item item) { return true if we have enough space}
          void add(Item item) {add item to vector of items, update remaining space}
          void do_timestep() {call do_timestep() and all Items, remove all items which indicate they are dead, updating remaining_space as you go}
          void print { print all the contents }
      }
      
      class BinCollection
      {
         void do_timestep { call do_timestep on all of the bins }
         void add(item item) { find first bin for which can_add return true, then add it, create a new bin if neccessary }
         void print() { print all the bins }
      }
      

      一些快速说明:

      • 在您的代码中,您反复将 int 大小转换为浮点数,这不是一个好主意。在我的设计中本地化到一个地方
      • 您会注意到,与单个项目相关的逻辑现在包含在项目本身内。其他对象只能看到对他们重要的内容、size_required 以及该对象是否还活着
      • 我没有包含任何关于排序的内容,因为我不清楚它在首次拟合算法中的用途。

      【讨论】:

      • 如果你给我一个实际问题的描述(因为它不是我熟悉的版本,我会给你一个伪代码版本,我将如何尝试解决它)
      • 最终目标是使用第一次拟合的动态在线装箱算法。这就是为什么我现在在代码中编写了诸如 life 和 check_dead 之类的东西。我的第一个目标只是完成一个首次拟合算法,因为让它动态不需要更多,然后只需引入一个检查并带走所有“死”的项目。项目大小也限制为 1 的一小部分,bin 大小始终为 1。感谢您的快速和翔实的回复
      • 这实际上是我一直在寻找的,因此不包括算法这一事实完全没问题。谢谢
      【解决方案3】:

      This interview 对 STL 背后的基本原理提供了一些深刻的见解。这可能会给您一些关于如何以 STL 方式实现算法的灵感。

      【讨论】:

      • 感谢您的阅读提示!很有趣,感觉它帮助我整理了我的想法。
      猜你喜欢
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-08
      • 1970-01-01
      • 2018-10-30
      • 2018-11-06
      • 2023-03-27
      相关资源
      最近更新 更多