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


9

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

質問の例:

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

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

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

以下の解決策では、r8レストランにスターごとに15 $を割り当てます。つまり、リストを生成するとき、リストに最初にそれを入れ、残りの70 $では、さらに4つ星を与える2つだけのスターを取得できます。ただし、r8レストランをスキップするのに十分なほど賢い場合は(星あたりの最高のドルの比率ですが)、r1100ドルのコストと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()

2
これはナップザックですか?私を許して、私はスキミングしました。
ケニー

1
これはナップザックと同じ概念です- budget=リュックサックの最大重量(kg)、max=ナップザックが保持できるアイテムの数、=アイテムのstars値とcost=アイテムの重量(kg)
AK47

3
そして、投稿されたコードの問題は何ですか?
cricket_007

1
@ cricket_007は順序に基づいて、スターごとに15 $をr8レストランに割り当てます。つまり、リストを生成するとき、それを最初にリストに入れ、残りの70 $でさらに2つ星を獲得できます。ただし、それをスキップするのに十分スマートである場合(スターあたりの最高のドルの比率であるにもかかわらずr1、100 $のコストと5つ星
AK47

回答:


5

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

ご存じかもしれませんが、ナップザック問題はNP困難です。つまり、多項式時間スケーリングを使用したアルゴリズムは知られていません。

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

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

こちらをご覧くださいhttps : //en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem

基本的にm、「マックススター」の配列を定義します。これ m[i, b, v]は、レストラン数まで(およびそれを含む)レストランを訪問し、i最大bで支出し、最大のvレストランを訪問する(制限)場合に取得できる星の最大量です。。

次に、この配列をボトムアップで入力します。たとえば、 m[0, b, v] = 0すべての値のためbv私たちはどのレストランに行くことができない場合、我々はすべての星を取得することはできませんので。

また、m[i, b, 0] = 0のすべての値についてibすべての訪問を使い果たしてしまうと、これ以上星を獲得できなくなります。

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

m[i, b, v] = m[i - 1, b, v] if p[i] > bp[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]星が増えます。これはの2番目の部分maxです。

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

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


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

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


多項式時間に関して、最大10のレストランは、問題がブルートフォースで解決できることを意味し、最大10のレストランのすべての組み合わせを繰り返し、O(n ^ 10)時間で最高のレストランを維持します。ここで、n(10 ^ 6)でO(n ^ 10)アルゴリズムを実行したくありませんが、それは多項式時間です。
kaya3

「10レストラン」は本当に固定数ですか、それとも上記の例では固定されており、別の例ではさらに大きくなる可能性がありますか?
Lagerbaer

これは良い質問であり、実行時間を分析するときに一般化すべき問題のパラメーターは明確ではありません。もちろん、kの多項式である既知の解はありません。kが小さい場合の問題にのみ関心がある場合、それは非常に弱い結論です。
kaya3

レストランの「最大」数は変わる可能性があります。この反復は、それは10であってもよく、次のことがあってもよい5
AK47

@ AK47とにかく、上でスケッチしたアルゴリズムはかなりきちんとしているはずです。多次元配列のサイズは、予算、レストランの数、訪問数によって決まり、配列の1つのエントリを埋めるにはO(1)がかかるため、アルゴは時間内に実行されますO(R B V)。
Lagerbaer

2

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

合計が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]);


@Jannes Botisさん、100,000のレストランで27秒かかっています:repl.it/repls/StripedMoralOptimization 100万件のレコードを処理するように最適化できると思いますか?
AK47

ボトルネックは、findCheapestRestaurant()内の.filter()関数です。レストランを作成した後、コストに応じてレストランをsort()し、filter()の代わりに.find()を使用することができます。リンクを変更しました。しかし、最善の解決策は、コストにインデックスを付けたレストランにデータベース(例:mysql)を使用することです。これにより、.filter()を条件付き選択に置き換えることができます。
Jannes Botis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.