Bir alana dayalı geniş bir nesne listesinin en verimli kombinasyonunu elde edin


9

Belirli bir bütçe ve kombinasyonda maksimum sınır verilen yıldız sayısını en üst düzeye çıkarmak istiyorum.

Örnek soru:

500 euroluk bir bütçeyle, yalnızca izin verilen maksimum veya daha az restoran ziyaret ederek, yemek yiyip mümkün olan en fazla yıldızı toplayın.

Potansiyel olarak 10 max restorana kadar 1 milyon restoran örneğini işleyebilecek verimli bir algoritma yazmak istiyorum.

Not: Bu, dün sorduğum bir sorudan bir çapraz gönderi: Java: Bir alana dayalı büyük bir Nesne Listesi'nin en verimli kombinasyonunu alın

Aşağıdaki çözüm, yıldız başına 15 $ r8 Restoran'a , yani listeyi oluştururken önce listeye koyar ve kalan 70 $ ile toplam 4 yıldız veren sadece 2 yıldız daha alabilirsiniz. r8Düz-se bile (yıldız oranı başına en iyi dolar olmasına rağmen) restoran atlamak için akıllı, ancak r1, 100 $ maliyet ve 5 yıldız olduğu gibi restoran aslında bütçe için daha iyi bir seçim olacaktır.

Herkes sorunu denemek ve mevcut çözümü yenmek yardımcı olabilir mi?

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
Bu sırt çantası mı? Affet beni, yağmaladım.
Kenny Ostrom

1
Sırt çantası ile aynı kavramdır - budget= kg cinsinden maksimum sırt çantası ağırlığı, max= sırt çantasının tutabileceği eşya sayısı, stars= eşya üzerinde bir miktar değer ve cost= kg olarak eşya ağırlığı
AK47

3
Gönderilen kodla ilgili sorun nedir?
OneCricketeer

1
@ cricket_007, siparişe göre, r8restorana yıldız başına 15 $ atar , yani listeyi oluştururken önce listeye koyar ve kalan 70 $ ile sadece 2 yıldız daha alabilir. Ancak, bunu atlamak için yeterince akıllı olsaydı (yıldız başına en iyi dolar olmasına rağmen r1, 100 $ maliyet ve 5 yıldız olduğu için restoran aslında bütçe için daha iyi bir seçim olurdu
AK47

Yanıtlar:


5

Sorununuz Knapsack sorunuyla hemen hemen aynı gibi görünüyor: Belirli ağırlık ve hacim kısıtlamaları göz önüne alındığında değeri en üst düzeye çıkarın. Temel olarak değer = toplam yıldız, ağırlık = fiyat, sırt çantası limiti = toplam bütçe. Şimdi toplam "ürün" (restoran ziyaretleri) ek bir kısıtlama var ama bu özü değişmez.

Bildiğiniz veya bilmediğiniz gibi, sırt çantası problemi NP zordur, yani polinom zaman ölçeği ile algoritma bilinmemektedir.

Bununla birlikte, dinamik programlama kullanarak etkili psödopolinom algoritmaları olabilir ve elbette keşfetmiş olduğunuz "açgözlü" sezgisel tarama gibi etkili sezgisel tarama vardır. Bu sezgisel tarama önce en yüksek "yoğunluk" öğeleri (kova başına en çok yıldız) ile doldurmaya başlamayı içerir. Gördüğünüz gibi, bu sezgisel tarama bazı durumlarda gerçek optimumluğu bulamıyor.

Dinamik programlama yaklaşımı burada oldukça iyi olmalıdır. Bir özyinelemeye dayanır: B bütçesi ve kalan ziyaret sayısı V göz önüne alındığında, toplam R restoranı kümesinden ziyaret etmek için en iyi restoranlar hangisidir?

Buraya bakın: https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem

Temel olarak m"maksimum yıldız" için bir dizi tanımlıyoruz. Burada m[i, b, v], restoran numarasına kadar (ve dahil) restoranlara kadar ziyaret etme, en ifazla harcama yapma ve en fazla ziyaret etme imkanımız olduğunda alabileceğimiz maksimum yıldız miktarıbv restoranı (sınır) için .

Şimdi, bu diziyi aşağıdan yukarıya dolduruyoruz. Örneğin, ve m[0, b, v] = 0öğelerinin tüm değerleri içinbv eğer herhangi bir restorana gidemezsek, yıldız alamayız.

Ayrıca, ve m[i, b, 0] = 0öğelerinin tüm değerleri içinib tüm ziyaretlerimizi tüketirsek, artık yıldız alamayız.

Sonraki satır da çok zor değil:

m[i, b, v] = m[i - 1, b, v] if p[i] > bp[i]restoranda yemek fiyatı nerede i. Bu satır ne diyor? Peki, eğer restoran iparamızdan daha pahalıysa ( b) o zaman oraya gidemeyiz. Bu, alabileceğimiz maksimum yıldız miktarının, restoranlara kadar iya da sadece kadar restoran dahil etsek aynı olduğu anlamına gelir i - 1.

Sonraki satır biraz zor:

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

Uf. s[i]restorandan alacağın yıldız miktarıibtw .

Bu satır ne diyor? Dinamik programlama yaklaşımının kalbidir. En fazla yıldız miktarını göz önünde bulundurarak, restoranlara kadar ve dahil olmak üzerei sonuçta ortaya çıkan çözümde oraya gidiyoruz ya da gitmiyoruz ve bu iki yoldan hangisinin daha fazla yol açtığını görmek zorundayız. yıldızlar:

Restorana gitmezsek i, aynı miktarda para ve kalan ziyaretleri tutarız. Bu yolda alabileceğimiz maksimum yıldız miktarı, restorana bile bakmadı gibi i. Bu ilk bölümü max.

Ama restorana gidersek i, o zaman p[i]daha az para, daha az ziyaret ve s[i]daha fazla yıldız kalırız . Bu ikinci bölüm max.

Şimdi soru basit: ikisinden hangisi daha büyük.

Bu diziyi oluşturabilir ve döngü için nispeten basit bir şekilde doldurabilirsiniz (wiki'den ilham alın). Bu sadece size, ancak gerçek restoranlar listesi ziyaret yıldız miktarı verir. Bunun için, hesabına biraz ekstra defter tutma ekleyin w.


Umarım bilgilerin sizi doğru yönde yönlendirmesi yeterlidir.

Alternatif olarak, probleminizi ikili değişkenler ve ikinci dereceden objektif bir fonksiyon olarak yazabilir ve D-Wave kuantum annelaer'da çözebilirsiniz :-p Bu konuda daha fazla bilgi edinmek istiyorsanız bana mesaj gönderin.


Polinom zamanı ile ilgili olarak, en fazla 10 restoran, sorunun kaba kuvvetle çözülebileceği, 10 restorana kadar olan tüm kombinasyonlarda tekrarlanabileceği ve en iyisi O (n ^ 10) zamanında tutulabileceği anlamına gelir. Şimdi, n = 10 ^ 6 ile bir O (n ^ 10) algoritması çalıştırmak istemiyorum, ama polinom zamanı.
kaya3

"10 restoran" gerçekten sabit bir sayı mıdır yoksa yukarıdaki örnekte mi sabittir ve farklı bir örnek için daha büyük olabilir mi?
Lagerbaer

Bu iyi bir soru ve çalışma süresi analiz edilirken sorunun hangi parametrelerinin genelleştirileceği belli değil. Tabii ki, k'de polinom olan bilinen bir çözüm yoktur, yani sadece küçük k sorunuyla ilgilenirsek, bunun oldukça zayıf bir sonuç olduğu anlamına gelir.
kaya3

"Maksimum" restoran sayısı değişebilir. Bu yineleme 10 olabilir ve sonra 5 olabilir.
AK47

@ AK47 Ne olursa olsun, yukarıda çizdiğim algoritma oldukça düzgün olmalı. Çok boyutlu dizinin boyutu bütçenize, restoran sayısına ve ziyaret sayısına göre verilir ve dizinin bir girişini doldurmak O (1) alır, böylece algo O (R zamanında çalışır) B V).
Lagerbaer

2

Aynı fikri kullanarak burada cevap :

S'yi toplayan n pozitif sayılar koleksiyonunda, bunlardan en az biri S'den n'ye bölünür (N / S)

potansiyel "en ucuz" restoranlardan başlayarak listeyi oluşturabilirsiniz .

Algoritmanın adımları:

  • Her biri farklı yıldızlara ve her yıldız için en düşük maliyete sahip <500/10 maliyeti olan 5 restoranı bulun . örneğin r1, r2, r3, r4, r5
  • Yukarıdaki değerlerin her biri için, maliyeti <(500 - maliyet (x)) / 9 ve farklı yıldızlara sahip 5 restoran daha bulun . Yine her yıldız için en düşük maliyeti seçin
  • bunu 10 restorana ulaşana ve bütçenizi aşana kadar yapın.
  • Yukarıdaki 3 adımı 1 - 9 restoran sınırı için tekrar çalıştırın.
  • En fazla yıldızı üreten çözümü koruyun

Tabii ki, bir restoranı yeniden seçemezsiniz.

Bence en kötü durum, 5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5 (= yaklaşık 12 milyon) çözümü hesaplamanız gerekecek.

Javascript'te

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


Hey @Jannes Botis, 100000 restoran için 27 saniye alıyor: repl.it/repls/StripedMoralOptimization 1 milyon kayıtla çalışacak şekilde optimize etmenin mümkün olduğunu düşünüyor musunuz?
AK47

Darboğaz findCheapestRestaurant () içindeki .filter () işlevidir, oluşturulduktan sonra maliyetteki restoranları sıralayabilir (). Bağlantıda değişiklik yaptım. Ama en iyi çözüm maliyet.
Jannes Botis
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.