web-dev-qa-db-ja.com

フィールドに基づいてオブジェクトの大きなリストの最も効率的な組み合わせを取得します

特定の予算と組み合わせの上限を考慮して、星の数を最大化しようとしています。

質問の例:

500ユーロの予算で、許可された最大数以下のレストランのみを訪れ、食事を取り、可能な限り多くの星を集めます。

最大10個のレストランの100万のRestaurantインスタンスを処理できる可能性のある、効率的なアルゴリズムを作成しようと思っています。

これは、私が昨日質問したクロスポストです: Java:フィールドに基づくオブジェクトの大きなリストの最も効率的な組み合わせを取得してください

以下の解決策では、スターあたり15 $をr8レストランに割り当てます。つまり、リストを生成するとき、リストに最初にそれを入れ、残りの70 $では、さらに2つのスターしか取得できず、合計が得られます4つ星。ただし、r8レストランをスキップするのに十分賢い場合は(星あたりの最高のドルの比率ですが)、100 $のコストとr1レストランのほうが実際には予算に適しています。 5つ星。

誰かが問題を試し、現在の解決策を打つ手助けをすることができますか?

import itertools

class Restaurant():
  def __init__(self, cost, stars):
    self.cost = cost
    self.stars = stars
    self.ratio = cost / stars

  def display(self):
    print("Cost: $" + str(self.cost))
    print("Stars: " + str(self.stars))
    print()

r1 = Restaurant(100, 5)
r2 = Restaurant(140, 3)
r3 = Restaurant(90, 4)
r4 = Restaurant(140, 3)
r5 = Restaurant(120, 4)
r6 = Restaurant(60, 1)
r7 = Restaurant(40, 1)
r8 = Restaurant(30, 2)
r9 = Restaurant(70, 2)
r10 = Restaurant(250, 5)

print()
print("***************")
print("** Unsorted: **")
print("***************")
print()

restaurants = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10]

for restaurant in restaurants:
  print(restaurant.ratio, restaurant.stars)

print()
print("***************")
print("**  Sorted:  **")
print("***************")
print()

sorted_restaurants = sorted(restaurants, key = lambda x: x.ratio, reverse = True)

for restaurant in sorted_restaurants:
  print(restaurant.ratio, restaurant.stars)

print()
print("*********************")
print("** Begin Rucksack: **")
print("*********************")
print()

max = 5
budget = 100

spent = 0
quantity = 0

rucksack = []

for i in itertools.count():

  if len(rucksack) >= max or i == len(sorted_restaurants):
    break

  sorted_restaurants[i].display()

  if sorted_restaurants[i].cost + spent <= budget:
    spent = spent + sorted_restaurants[i].cost
    rucksack.append(sorted_restaurants[i])

print("Total Cost: $" + str(sum([x.cost for x in rucksack])))
print("Total Stars: " + str(sum([x.stars for x in rucksack])))

print()
print("*****************")
print("** Final List: **")
print("*****************")
print()

for restaurant in rucksack:
  restaurant.display()
9
AK47

あなたの問題のように聞こえるのはナップザック問題とほとんど同じです:特定の重量と体積の制約を与えられた値を最大化します。基本的に、値=星の合計、重量=価格、リュックサックの制限=予算の合計。ここで、「アイテム」(レストランへの訪問)の合計に追加の制約がありますが、それは要点を変更しません。

ご存じかもしれませんが、ナップザック問題はNP hardです。これは、多項式時間スケーリングのアルゴリズムが知られていないことを意味します。

ただし、動的プログラミングを使用した効率的な疑似多項式アルゴリズムが存在する場合があり、もちろん、発見したように見える「貪欲な」ヒューリスティックなどの効率的なヒューリスティックがあります。このヒューリスティックでは、最初に最も「密度の高い」アイテム(1ドルあたりの星の数)でいっぱいになり始めます。ご覧のように、このヒューリスティックはいくつかのケースで真の最適を見つけることができません。

動的プログラミングのアプローチは、ここではかなり良いはずです。これは再帰に基づいています。予算Bと残りの訪問数Vが与えられた場合、レストランの全セットRから訪問するのに最適なレストランのセットは何ですか?

ここを参照してください: https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem

基本的に、「max stars」の配列mを定義します。ここで、_m[i, b, v]_は、レストラン番号i、最大bの費用、最大vのレストランを訪問(上限)。

次に、この配列をボトムアップで入力します。たとえば、bvのすべての値の_m[0, b, v] = 0_は、レストランに行けないと星を獲得できないためです。

また、ibのすべての値に対して_m[i, b, 0] = 0_を使用します。これは、すべての訪問を使い果たすと、これ以上星を取得できないためです。

次の行も難しくありません:

_m[i, b, v] = m[i - 1, b, v] if p[i] > b_ここで_p[i]_はレストランiでの食事の価格です。この行は何を言っていますか?まあ、レストランiが私たちの残金(b)よりも高い場合、そこに行くことはできません。つまり、取得できる星の最大数は、iまでのレストランを含めても、_i - 1_までのレストランを含めても同じです。

次の行は少しトリッキーです:

m[i, b, v] = max(m[i-1, b, v]), m[i-1, b - p[i], v-1] + s[i]) if p[i] <= b

ふew。 _s[i]_は、レストランiから獲得した星の数です。

この行は何を言っていますか?これは、動的プログラミングアプローチの中心です。 iまでのレストランを見たときに取得できる星の最大量を検討するとき、結果のソリューションでは、そこに行くか、行かず、どちらかを「ちょうど」確認する必要があります。これらの2つのパスはより多くの星につながります:

レストランiに行かない場合、同じ金額と残りの訪問を維持します。このパスで取得できる星の最大数は、レストランiを見ていない場合と同じです。これがmaxの最初の部分です。

しかし、レストランiに行くと、_p[i]_少ないお金で、訪問数が1つ少なくなり、_s[i]_星が残ります。これがmaxの2番目の部分です。

質問は簡単です。2つのうちどちらが大きいかです。

この配列を作成して、比較的単純なforループで埋めることができます(wikiからインスピレーションを得ます)。ただし、実際に訪れるレストランのリストではなく、星の数だけが表示されます。そのためには、wの計算に追加の簿記を追加します。


情報があなたを正しい方向に導くのに十分であることを願っています。

あるいは、バイナリ変数と2次の目的関数の観点から問題を記述し、D-Wave量子アネラーで解くことができます。

5
Lagerbaer

ここに私の答え と同じアイデアを使用します:

合計がSになるn個の正の数値のコレクションでは、それらの少なくとも1つは、Sをnで割った値(S/n)よりも小さくなります。

あなたは、リストを作成することができます潜在的な「最も安い」レストランから始めます。

アルゴリズムのステップ:

  • コストが500/10未満の5つのレストランを見つけ、それぞれに異なる星と、各星の最も低いコスト。例:r1、r2、r3、r4、r5
  • 上記の各値について、コスト<(500-cost(x))/ 9および異なる星の5つのレストランを見つけます。各スターの最低コストを再度選択します
  • 10のレストランに到達し、予算を超えないまで、これを実行します。
  • 上記の3つのステップを1〜9のレストラン制限で再実行します。
  • 最も多くの星を生み出すソリューションを維持する

もちろん、レストランを再選択することはできません。

最悪の場合、5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5(=約1200万)のソリューションを計算する必要があると思います。

JavaScriptで

function Restaurant(name, cost, stars) {
    this.name = name;
    this.cost = cost;
    this.stars = stars;
}

function RestaurantCollection() {
    var restaurants = [];
    var cost = 0;
    this.stars = 0;

    this.addRestaurant = function(restaurant) {
        restaurants.Push(restaurant);
        cost += restaurant.cost;
        this.stars += restaurant.stars;
    };

    this.setRestaurants = function(clonedRestaurants, nCost, nStars) {
        restaurants = clonedRestaurants;
        cost = nCost;
        this.stars += nStars;
    };
    this.getAll = function() {
        return restaurants;
    };

    this.getCost = function() {
        return cost;
    };
    this.setCost = function(clonedCost) {
        cost = clonedCost;
    };

    this.findNext5Restaurants = function(restaurants, budget, totalGoal) {
        var existingRestaurants = this.getAll();
        var maxCost = (budget - cost) / (totalGoal - existingRestaurants.length);
        var cheapestRestaurantPerStarRating = [];
        for(var stars = 5; stars > 0; stars--) {
            var found = findCheapestRestaurant(restaurants, stars, maxCost, existingRestaurants);
            if(found) {
                cheapestRestaurantPerStarRating.Push(found);
            }
        }
        return cheapestRestaurantPerStarRating;
    };

    this.clone = function() {
        var restaurantCollection = new RestaurantCollection();
        restaurantCollection.setRestaurants([...restaurants], this.getCost(), this.stars);
        return restaurantCollection;
    };
}

function findCheapestRestaurant(restaurants, stars, maxCost, excludeRestaurants) {
     var excludeRestaurantNames = excludeRestaurants.map(restaurant => restaurant.name);
     var found = restaurants.find(restaurant => restaurant.stars == stars && restaurant.cost <= maxCost && !excludeRestaurantNames.includes(restaurant.name));
     return found;
}

function calculateNextCollections(restaurants, collections, budget, totalGoal) {
    var newCollections = [];
    collections.forEach(collection => {
        var nextRestaurants = collection.findNext5Restaurants(restaurants, budget, totalGoal);
        nextRestaurants.forEach(restaurant => {
            var newCollection = collection.clone();
            newCollection.addRestaurant(restaurant);
            if(newCollection.getCost() <= budget) {
                 newCollections.Push(newCollection);
            }
        });
    });
    return newCollections;
};

var restaurants = [];
restaurants.Push(new Restaurant('r1', 100, 5));
restaurants.Push(new Restaurant('r2',140, 3));
restaurants.Push(new Restaurant('r3',90, 4));
restaurants.Push(new Restaurant('r4',140, 3));
restaurants.Push(new Restaurant('r5',120, 4));
restaurants.Push(new Restaurant('r6',60, 1));
restaurants.Push(new Restaurant('r7',40, 1));
restaurants.Push(new Restaurant('r8',30, 2));
restaurants.Push(new Restaurant('r9',70, 2));
restaurants.Push(new Restaurant('r10',250, 5));

restaurants.sort((a, b) => a.cost - b.cost);
var max = 5;
var budget = 100;

var total = max;
var totalCollections = [];

for(var totalGoal = total; totalGoal > 0; totalGoal--) {
    var collections = [new RestaurantCollection()];

    for(var i = totalGoal; i > 0; i--) {
        collections = calculateNextCollections(restaurants, collections, budget, totalGoal);
    }
    totalCollections = totalCollections.concat(collections);
}

var totalCollections = totalCollections.map(collection => { 
      return {
          name: collection.getAll().map(restaurant => restaurant.name),
          stars: collection.stars,
          cost: collection.getCost()
      }
});

console.log("Solutions found:\n");
console.log(totalCollections);

totalCollections.sort((a, b) => b.stars - a.stars);
console.log("Best solution:\n");
console.log(totalCollections[0]);
2
Jannes Botis