Nhận kết hợp hiệu quả nhất của Danh sách đối tượng lớn dựa trên một trường


9

Tôi đang tìm cách tối đa hóa số lượng sao cho một ngân sách nhất định và giới hạn tối đa cho kết hợp.

Câu hỏi ví dụ:

Với ngân sách 500 euro, chỉ ghé thăm các nhà hàng được phép tối đa hoặc ít hơn, dùng bữa và thu thập nhiều ngôi sao nhất có thể.

Tôi đang tìm cách viết một thuật toán hiệu quả, có khả năng xử lý 1 triệu trường hợp Nhà hàng cho tối đa 10 Nhà hàng tối đa.

Lưu ý, đây là một bài đăng chéo từ một câu hỏi tôi đã hỏi ngày hôm qua: Java: Nhận kết hợp hiệu quả nhất của Danh sách đối tượng lớn dựa trên một trường

Giải pháp bên dưới sẽ chỉ định 15 đô la cho mỗi sao cho r8Nhà hàng, điều đó có nghĩa là khi tạo danh sách, nó sẽ đưa danh sách đó vào danh sách trước và với 70 đô la còn lại, chỉ có thể nhận thêm 2 sao cho tổng cộng 4 sao. Tuy nhiên, nếu đủ thông minh để bỏ qua r8nhà hàng (mặc dù đó là tỷ lệ đô la trên sao tốt nhất), r1nhà hàng thực sự sẽ là lựa chọn tốt hơn cho ngân sách, vì chi phí 100 đô la và 5 sao.

Bất cứ ai có thể giúp cố gắng vấn đề và đánh bại các giải pháp hiện tại?

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
Đây có phải là ba lô không? Tha lỗi cho tôi, tôi lướt qua.
Kenny Ostrom

1
Đó là cùng một khái niệm về ba lô - budget= trọng lượng ba lô tối đa tính bằng kg, max= số lượng vật phẩm mà chiếc ba lô có thể giữ, stars= một số giá trị trên vật phẩm và cost= trọng lượng vật phẩm tính bằng kg
AK47

3
Và vấn đề với mã được đăng là gì?
cricket_007

1
@ cricket_007 dựa trên đơn đặt hàng, nó chỉ định 15 đô la mỗi sao cho r8Nhà hàng, điều đó có nghĩa là khi tạo danh sách, nó sẽ đưa danh sách đó vào danh sách trước và với 70 đô la còn lại, chỉ có thể nhận thêm 2 sao. Tuy nhiên, nếu đủ thông minh để bỏ qua điều đó (mặc dù đó là tỷ lệ đô la trên sao tốt nhất, r1nhà hàng thực sự sẽ là lựa chọn tốt hơn cho ngân sách, vì đó là chi phí 100 đô la và 5 sao
AK47

Câu trả lời:


5

Âm thanh giống như vấn đề của bạn khá giống với vấn đề Knapsack: Tối đa hóa giá trị với các ràng buộc về trọng lượng và âm lượng nhất định. Về cơ bản giá trị = tổng số sao, trọng lượng = giá, giới hạn ba lô = tổng ngân sách. Bây giờ có một ràng buộc bổ sung của tổng số "mặt hàng" (thăm nhà hàng) nhưng điều đó không thay đổi ý chính.

Như bạn có thể biết hoặc không, vấn đề về chiếc ba lô là NP cứng, điều đó có nghĩa là không có thuật toán nào với tỷ lệ thời gian đa thức được biết đến.

Tuy nhiên, có thể có các thuật toán giả ngẫu nhiên hiệu quả bằng cách sử dụng lập trình động, và tất nhiên có các thuật toán trị liệu hiệu quả, chẳng hạn như heuristic "tham lam" mà bạn dường như đã phát hiện ra. Heuristic này liên quan đến việc bắt đầu lấp đầy các vật phẩm "mật độ" cao nhất (hầu hết các sao trên mỗi xô) trước tiên. Như bạn đã thấy, heuristic này không tìm thấy tối ưu thực sự trong một số trường hợp.

Cách tiếp cận lập trình động nên khá tốt ở đây. Nó dựa trên một đệ quy: Với ngân sách B và một số lượt truy cập còn lại V, tập hợp các nhà hàng tốt nhất để ghé thăm trong tổng số các nhà hàng R là gì?

Xem tại đây: https://en.wikipedia.org/wiki/Knapsack_probols#0/1_knapsack_probols

Về cơ bản, chúng tôi xác định một mảng mcho "sao tối đa", m[i, b, v]số lượng sao tối đa chúng tôi có thể nhận được khi chúng tôi được phép ghé thăm nhà hàng lên đến (và bao gồm) số nhà hàng i, chi tiêu nhiều nhất bvà ghé thăm hầu hết các vnhà hàng (giới hạn) .

Bây giờ, chúng tôi từ dưới lên điền vào mảng này. Ví dụ: m[0, b, v] = 0đối với tất cả các giá trị bvbởi vì nếu chúng tôi không thể đến bất kỳ nhà hàng nào, chúng tôi sẽ không nhận được bất kỳ ngôi sao nào.

Ngoài ra, m[i, b, 0] = 0đối với tất cả các giá trị ibbởi vì nếu chúng tôi sử dụng hết tất cả các lượt truy cập của mình, chúng tôi không thể nhận thêm bất kỳ ngôi sao nào.

Dòng tiếp theo cũng không quá khó:

m[i, b, v] = m[i - 1, b, v] if p[i] > b nơi p[i]là giá của bữa tại nhà hàng i. Dòng này nói gì? Chà, nếu nhà hàng iđắt hơn chúng tôi còn tiền ( b) thì chúng tôi không thể đến đó. Điều đó có nghĩa là số lượng sao tối đa chúng ta có thể nhận được là như nhau cho dù chúng tôi có bao gồm các nhà hàng lên đến ihay chỉ tối đa i - 1.

Dòng tiếp theo là một chút khó khăn:

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

Phù. s[i]là số lượng sao bạn nhận được từ nhà hàng ibtw.

Dòng này nói gì? Đó là trung tâm của phương pháp lập trình động. Khi xem xét số lượng sao tối đa chúng ta có thể nhận được khi xem xét các nhà hàng lên đến và bao gồm i, sau đó, trong giải pháp kết quả chúng ta sẽ đến đó hoặc chúng ta không, và chúng ta "chỉ" phải xem con đường nào trong hai con đường này dẫn đến nhiều hơn sao:

Nếu chúng tôi không đến nhà hàng i, thì chúng tôi sẽ giữ nguyên số tiền và số lượt truy cập còn lại. Số lượng sao tối đa chúng ta có thể nhận được trong đường dẫn này giống như khi chúng ta thậm chí không nhìn vào nhà hàng i. Đó là phần đầu tiên trong max.

Nhưng nếu chúng ta đi đến nhà hàng i, thì chúng ta sẽ có p[i]ít tiền hơn, một lần ghé thăm ít hơn và s[i]nhiều ngôi sao hơn. Đó là phần thứ hai trong max.

Bây giờ câu hỏi rất đơn giản: cái nào trong hai cái lớn hơn.

Bạn có thể tạo mảng này và điền vào nó một vòng lặp tương đối đơn giản (lấy cảm hứng từ wiki). Điều này chỉ cung cấp cho bạn số lượng sao, chứ không phải danh sách thực tế của các nhà hàng sẽ ghé thăm. Đối với điều đó, thêm một số sổ sách kế toán bổ sung để tính toán w.


Tôi hy vọng rằng thông tin đó là đủ để đưa bạn đi đúng hướng.

Ngoài ra, bạn có thể viết vấn đề của mình theo các biến nhị phân và hàm mục tiêu bậc hai và giải quyết vấn đề đó trên phần tử lượng tử D-Wave :-p Nhắn tin cho tôi nếu bạn muốn biết thêm về điều đó.


Về thời gian đa thức, tối đa 10 nhà hàng có nghĩa là vấn đề có thể được giải quyết bằng vũ lực, lặp đi lặp lại qua tất cả các kết hợp của tối đa 10 nhà hàng và giữ tốt nhất, trong thời gian O (n ^ 10). Bây giờ, tôi không muốn chạy thuật toán O (n ^ 10) với n = 10 ^ 6, nhưng đó là thời gian đa thức.
kaya3

Là "10 nhà hàng" là một con số thực sự cố định, hoặc chỉ cố định trong ví dụ trên, và có thể lớn hơn cho một ví dụ khác?
Lagerbaer

Đó là một câu hỏi hay và không rõ các thông số của vấn đề sẽ được khái quát hóa khi phân tích thời gian chạy. Tất nhiên, không có giải pháp nào được biết là đa thức trong k, tôi chỉ có nghĩa đó là một kết luận khá yếu nếu chúng ta chỉ quan tâm đến vấn đề cho k nhỏ.
kaya3

Số lượng nhà hàng "tối đa" có thể thay đổi. Lần lặp này có thể là 10 và tiếp theo có thể là 5.
AK47

@ AK47 Bất kể, thuật toán tôi phác thảo ở trên nên khá gọn gàng. Kích thước của mảng đa chiều được cung cấp bởi ngân sách của bạn, số lượng nhà hàng và số lượt truy cập và phải mất O (1) để điền vào một mục của mảng, do đó, thuật toán chạy trong thời gian O (R B V).
Lagerbaer

2

Sử dụng cùng một ý tưởng như câu trả lời của tôi ở đây :

Trong tập hợp n số dương có tổng bằng S, ít nhất một trong số chúng sẽ nhỏ hơn S chia cho n (S / n)

bạn có thể xây dựng danh sách bắt đầu từ các nhà hàng "rẻ nhất" tiềm năng .

Các bước của thuật toán:

  • Tìm 5 nhà hàng có chi phí <500/10, mỗi nhà hàng có các ngôi sao khác nhauchi phí thấp nhất cho mỗi ngôi sao . ví dụ: r1, r2, r3, r4, r5
  • Đối với mỗi giá trị trên, hãy tìm 5 nhà hàng khác với chi phí <(500 - chi phí (x)) / 9 và các ngôi sao khác nhau . Một lần nữa chọn chi phí thấp nhất cho mỗi ngôi sao
  • làm điều này cho đến khi bạn đạt đến 10 nhà hàng và bạn không vượt quá ngân sách của mình.
  • Chạy lại 3 bước trên cho giới hạn 1 - 9 nhà hàng.
  • Giữ giải pháp tạo ra nhiều ngôi sao nhất

Tất nhiên, bạn không thể chọn lại một nhà hàng.

Tôi nghĩ trường hợp xấu nhất, bạn sẽ phải tính 5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5 (= khoảng 12 triệu) giải pháp.

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


Xin chào @Jannes Botis, Mất 27 giây cho 100000 Nhà hàng: repl.it/repls/StripedMoralOptimization Bạn có nghĩ rằng có thể tối ưu hóa nó để hoạt động với các bản ghi 1 triệu không?
AK47

Nút cổ chai là hàm .filter () bên trong findCheapestR Restaurant (), bạn có thể sắp xếp () các nhà hàng có chi phí sau khi chúng được tạo và sử dụng .find () thay vì bộ lọc () vì chỉ tìm thấy thứ 1 sẽ rẻ nhất. Tôi đã thực hiện thay đổi trong liên kết. Nhưng tôi nghĩ giải pháp tốt nhất sẽ là sử dụng cơ sở dữ liệu (ví dụ: mysql) cho các nhà hàng có chỉ số về chi phí, để bạn có thể thay thế .filter () bằng lựa chọn có điều kiện.
Jannes Botis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.