필드를 기반으로 큰 객체 목록을 가장 효율적으로 조합


9

특정 예산과 조합에 대한 최대 한도를 고려하여 별 수를 최대화하려고합니다.

질문 예 :

500 유로의 예산으로 최대 허용 레스토랑 이하를 방문하여 식사를하고 가장 많은 별을 모으십시오.

최대 10 개의 최대 식당에 대해 1 백만 개의 식당 인스턴스를 처리 할 수있는 효율적인 알고리즘을 작성하려고합니다.

참고 : 이것은 어제 내가 물었던 질문의 교차 게시물입니다. Java : 필드를 기반으로 큰 객체 목록을 가장 효율적으로 조합하십시오.

아래 솔루션은 별표 당 15 $를 r8식당에 할당합니다. 즉, 목록을 생성 할 때 목록에 우선 순위를두고 나머지 70 $로만 별 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
이거 배낭이야? 용서하세요.
Kenny Ostrom

1
배낭과 같은 개념입니다. budget= = 배낭 최대 무게, kg max= 배낭이 담을 수있는 품목의 수 , = 품목의 stars일부 가치 및 costkg의 품목 무게
AK47

3
그리고 코드가 게시 된 문제는 무엇입니까?
cricket_007

1
@ cricket_007은 주문에 따라 별표 당 15 $를 r8레스토랑에 할당합니다. 즉, 목록을 생성 할 때 목록에 먼저 넣고 나머지 70 $로 별을 2 개 더 얻을 수 있습니다. 그러나, 그것을 건너 뛰기에 충분히 현명하다면 (스타 당 최고의 달러 임에도 불구하고, r1식당은 실제로 100 $ 비용과 별 5 개이기 때문에 예산에 더 나은 선택이 될 것입니다
AK47

답변:


5

당신의 문제처럼 들리는 것은 배낭 문제와 거의 같습니다. 특정 무게와 부피 제한이 주어지면 가치를 극대화하십시오. 기본적으로 가치 = 총 별, 무게 = 가격, 배낭 한도 = 총 예산. 이제 총 "항목"(레스토랑 방문)에 대한 추가 제한이 있지만 요점을 변경하지는 않습니다.

알다시피 또는 모르는 것처럼 배낭 문제는 NP 하드이므로 다항식 시간 스케일링을 사용하는 알고리즘을 알 수 없습니다.

그러나 동적 프로그래밍을 사용하는 효율적인 의사 다항식 알고리즘이있을 수 있으며 물론 발견 한 "욕심쟁이"휴리스틱과 같은 효율적인 휴리스틱이 있습니다. 이 휴리스틱에는 가장 높은 "밀도"항목 (벅당 가장 많은 별)이 먼저 채워지기 시작합니다. 보시다시피,이 휴리스틱은 경우에 따라 실제 최적을 찾지 못합니다.

동적 프로그래밍 접근 방식은 여기에서 꽤 좋을 것입니다. 재귀를 기반으로합니다. 예산 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모든 가치를 위해 i그리고 b우리가 모든 방문을 다 사용하면 더 이상 별을 얻을 수 없기 때문에.

다음 줄도 그렇게 어렵지 않습니다.

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

s[i]식당 ibtw 에서 얻는 별의 양입니다 .

이 줄은 무엇을 말합니까? 동적 프로그래밍 방식의 핵심입니다. 를 포함하여 레스토랑을 볼 때 얻을 수있는 별의 최대량을 고려할 때 i, 결과 솔루션에서 우리는 거기에 가거나 가지 않습니다. 그리고 우리는이 두 경로 중 어느 것이 더 많은 것으로 이어지는 지 볼 수 있습니다. 별 :

우리가 식당에 가지 않으면 i같은 금액의 돈과 방문을 유지합니다. 이 길에서 얻을 수있는 별의 최대량은 마치 식당을 보지 않은 것과 같습니다 i. 의 첫 번째 부분입니다 max.

그러나 우리가 식당에 가면 돈이 적고 방문 횟수가 줄어들고 별이 더 많이 i남습니다 . 의 두 번째 부분입니다 .p[i]s[i]max

이제 질문은 간단합니다. 둘 중 큰 것이 더 큽니다.

이 배열을 만들고 비교적 간단한 for 루프로 채울 수 있습니다 (wiki에서 영감을 얻음). 이것은 방문 할 실제 레스토랑 목록이 아니라 별의 양을 제공합니다. 이를 위해의 계산에 추가 부기를 추가하십시오 w.


정보가 당신을 올바른 방향으로 이끌 수 있기를 바랍니다.

또는 이진 변수 및 2 차 목적 함수로 문제를 작성하고 D-Wave 양자 annelaer에서 문제를 해결할 수 있습니다.


다항식 시간과 관련하여 최대 10 개의 레스토랑은 최대 10 개 레스토랑의 모든 조합을 반복하고 O (n ^ 10) 시간 내에 최고의 레스토랑을 유지하면서 무차별 대입으로 문제를 해결할 수 있음을 의미합니다. 이제 n = 10 ^ 6 인 O (n ^ 10) 알고리즘을 실행하고 싶지 않지만 다항식 시간입니다.
kaya3

"10 레스토랑"은 고정 된 숫자입니까, 아니면 위의 예에서는 고정되어 있고 다른 예에서는 더 클 수 있습니까?
Lagerbaer 2014

좋은 질문이며 실행 시간을 분석 할 때 어떤 매개 변수를 일반화해야하는지 명확하지 않습니다. 물론, 다항식 k로 알려진 알려진 해결책은 없습니다. 작은 k의 문제에만 관심이 있다면 상당히 약한 결론입니다.
kaya3

"최대"레스토랑 수는 변경 될 수 있습니다. 이 반복은 10 일 수 있고 다음은 5 일 수 있습니다.
AK47

@ AK47 어쨌든 위에서 스케치 한 알고리즘은 매우 깔끔해야합니다. 다차원 배열의 크기는 예산, 레스토랑 수 및 방문 수에 따라 지정되며 배열의 한 항목을 채우려면 O (1)이 필요하므로 algo는 시간 O (R B V).
Lagerbaer 2014

2

내 대답 과 동일한 아이디어를 사용 하십시오 .

S에 합산되는 n 개의 양수 모음에서 적어도 하나는 S를 n으로 나눈 값 (S / n)보다 작습니다.

잠재적 인 "가장 싼"식당에서 시작 하여 목록을 작성할 수 있습니다 .

알고리즘의 단계 :

  • 각각 다른 별각 별에 대해 가장 저렴한 비용으로 <500/10의 5 개 레스토랑을 찾으십시오 . 예 : r1, r2, r3, r4, r5
  • 위의 각 값에 대해 비용이 <(500-cost (x)) / 9이고 별이 다른 5 개의 레스토랑을 찾으십시오 . 각 별마다 최저 비용을 다시 선택하십시오.
  • 당신이 10 레스토랑에 도달하고 예산을 초과하지 않을 때 까지이 작업을 수행 하십시오.
  • 1-9 개의 식당 한도에 대해 위의 3 단계를 다시 실행하십시오.
  • 별을 가장 많이 생성하는 솔루션 유지

물론 식당을 다시 선택할 수는 없습니다.

최악의 경우 5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5 (= 약 1,200 만) 솔루션을 계산해야한다고 생각합니다.

자바 스크립트에서

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 님, 100000 개의 레스토랑에 27 초가 걸립니다 : repl.it/repls/StripedMoralOptimization 1 백만 개의 레코드로 작업하도록 최적화 할 수 있다고 생각하십니까?
AK47

병목 현상은 findCheapestRestaurant () 내부의 .filter () 함수입니다. 레스토랑을 만든 후 비용에 따라 분류하고 () 대신 .find () 대신 filter () 대신 .find ()를 사용할 수 있습니다. 링크를 변경했습니다. 그러나 가장 좋은 해결책은 비용에 인덱스가있는 식당에 데이터베이스 (예 : mysql)를 사용하는 것이므로 .filter ()를 조건부 선택으로 바꿀 수 있습니다.
Jannes Botis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.