2

このサイトを使用するのは初めてなので、フォーマットが不適切であったり、変な表現があったりして申し訳ありません。このサイトのルールに準拠するように最善を尽くしますが、最初は間違いを犯す可能性があります。

私は現在、STL コンテナーを使用して C++ でいくつかの異なるビン パッキング アルゴリズムの実装に取り​​組んでいます。現在のコードには、修正が必要な論理的な障害がまだいくつかありますが、この質問はプログラムの構造に関するものです。論理的な障害の数を最小限に抑え、可能な限り読みやすくするためにプログラムをどのように構築する必要があるかについて、セカンドオピニオンを求めたくはありません。現在の状態では、これが最善の方法ではないと感じていますが、現在、コードを記述する他の方法は実際には見当たりません。

この問題は、動的なオンライン ビン パッキングの問題です。アイテムが割り当てられたビンを離れる前に、アイテムが任意の時間を持つという意味で動的です。

要するに、私の質問は次のとおり
です。ビン パッキング アルゴリズムの構造は 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;
}

これは私のコードの単なるスナップショットであり、まだ正しく実行されていないことに注意してください

関係のないおしゃべりでこれを混乱させたくないだけです。貢献してくれた人々に感謝したいだけです。コードを見直して、プログラミングをもう少しうまく構築できるようになることを願っています。

4

3 に答える 3

2

ビン パッキング アルゴリズムの構造は C++ ではどのようになりますか?

理想的には、アルゴリズムのロジックのみが異なる複数のビン パッキング アルゴリズムを異なる関数に分けて用意することをお勧めします。そのアルゴリズムは、データの表現から大きく独立している必要があるため、関数を 1 回呼び出すだけでアルゴリズムを変更できます。

STL アルゴリズムの共通点を見ることができます。主に、コンテナーではなくイテレーターで動作しますが、以下で詳しく説明するように、最初はこれをお勧めしません。どのようなアルゴリズムが利用可能かを把握し、それらを実装に活用する必要があります。

STL コンテナは、実装で任意の長さの入力を処理できるようにするための優れたツールですか?

通常は次のように動作します: コンテナを作成し、コンテナを満たし、コンテナにアルゴリズムを適用します。

要件の説明から判断すると、このように使用するので、問題ないと思います。ビン パッキング アルゴリズムとほとんどの STL アルゴリズムには重要な違いが 1 つあります。

STL アルゴリズムは、変更を行わないか、要素を宛先に挿入します。一方、ビンパッキングは、「これがビンのリストです。それらを使用するか、新しいビンを追加してください」。イテレータでこれを行うことは不可能ではありませんが、おそらく努力する価値はありません。コンテナを操作することから始め、動作するプログラムを取得してバックアップし、イテレータのみで動作するようにできるかどうかを確認します。

コンテナーを読みやすく、実装しやすい方法で処理するにはどうすればよいですか?

私はこのアプローチを採用し、入力と出力を特徴付けます。

  • 入力: 項目のコレクション、任意の長さ、任意の順序。
  • 出力: アルゴリズムによって決定されたビンのコレクション。各ビンにはアイテムのコレクションが含まれています。

それから、「私のアルゴリズムは何をする必要があるのか​​?」と心配します。

  • ゴミ箱を常にチェックして、「このアイテムは適合しますか?」
  • あなたClass_binは必要なものをうまくカプセル化しています。
  • 「print()」のような関係のないものでコードを乱雑にしないでください。非メンバーのヘルプ関数を使用してください。

type_item

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

life(または死) が使用されるかは不明です。その概念がビンパッキングアルゴリズムの実装に関連しているとは想像できません。多分それは残すべきですか?

operator<これは個人的な好みですが、自分のオブジェクトに与えるのは好きではありません。通常、オブジェクトは自明ではなく、「より少ない」には多くの意味があります。たとえば、あるアルゴリズムでは、死んだアイテムの前にすべての生きているアイテムを並べ替えることができます。わかりやすくするために、通常は別の構造体でラップします。

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);

Class_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_ます - それは少し過剰であり、コードから明らかなはずです. (これはハンガリアン表記法の変種です。プログラマーはこれに対して敵対する傾向があります。)
  • iクラス メンバー(イテレータ)を持つべきではありません。クラス状態の一部ではありません。すべてのメンバーでそれが必要な場合は、そこで再宣言するだけで問題ありません。入力するには長すぎる場合は、typedef.
  • 「bin1 が bin2 より小さい」という数値化は難しいため、. を削除することをお勧めしoperator<ます。
  • bool full(type_item)少し誤解を招くです。私はおそらく使用しますbool can_hold(type_item)。私には、残りのスペースがゼロbool full()の場合は true を返します。
  • check_load()より明確に名前が付けられているように見えますload()
  • 繰り返しますが、何check_dead()を達成することになっているのかは不明です。
  • print_binオブジェクトをきれいに保つために、それを削除して非メンバー関数として書くことができると思います。
  • StackOverflow の何人かは私を撃つだろうが、私はこれを構造体にして、load と item リストを public にしておくことを検討したい。ここでは、カプセル化についてあまり気にしていないようです (このオブジェクトを作成するだけでよいので、毎回負荷を再計算する必要はありません)。

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));
}
于 2010-07-10T07:12:22.957 に答える
1

いくつかの考え:

あなたの名前はところどころめちゃくちゃです。

  1. 入力という名前のパラメーターがたくさんありますが、それは意味がありません
  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 を必要とするのでしょうか? それは誰のビジネスでもありませんが、オブジェクト自体です。そのクラスに必要な public メソッドは、次のようなものでなければなりません * アイテムをビンのコレクションに追加する * ソリューションを出力する * 未来に 1 タイムステップ進む

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 サイズを float に繰り返し変換しましたが、これは良い考えではありません。1 つの場所にローカライズされた私のデザインでは
  • 単一のアイテムに関連するロジックがアイテム自体の中に含まれていることに気付くでしょう。他のオブジェクトは、それらにとって重要なもの、size_required、およびオブジェクトがまだ生きているかどうかのみを確認できます
  • 並べ替えについては何も含めていません。なぜなら、それが最初に適合するアルゴリズムで何のためにあるのかはっきりしないからです。
于 2010-07-10T06:26:10.807 に答える
0

このインタビューは、STL の背後にある理論的根拠についての優れた洞察を提供します。これは、アルゴリズムを STL 方式で実装する方法についてのインスピレーションを与えるかもしれません。

于 2010-07-10T09:18:47.263 に答える