Как найти наилучшую комбинацию множества фрагментов данных в зависимости от определенных критериев? - PullRequest
5 голосов
/ 05 сентября 2010

Я пытаюсь написать скрипт на Python, который находит комбинации предметов брони из игры, которые соответствуют определенным критериям. У меня есть объект, в котором есть ключи для каждого слота предмета (т. Е. Голова, грудь, талия и т. Д.), И список всех предметов, которые могут поместиться в этом слоте, с их характеристиками в каждом ключе. Есть 10 слотов и много предметов для каждого до 88 или около того предметов.

Мой вопрос: существует ли какой-то алгоритм, уже используемый для подобных вещей? Пример того, что я хотел бы сделать, - найти комбинацию частей брони, которая дает мне stat1 <35, который имеет самый высокий stat2 + 3 + 4. </p>

Я не верю, что грубое принуждение было бы практичным, потому что это заняло бы целую вечность (поправьте меня, если я ошибаюсь). Любая помощь будет оценена!

Редактировать - Подробнее:

Образец данных: http://pastebin.com/rTH3Q5Sj Первый кортеж состоит из двух элементов в слоте для головы, 2-й кортеж - из двух элементов в слоте для груди.

Одна вещь, которую я мог бы сделать с образцами данных, это получить комбинацию шлема и грудной клетки, которая имеет наивысшее общее количество ударов / ударов / пробиваний, но менее 12 суммарных обременений.

Ответы [ 2 ]

4 голосов
/ 05 сентября 2010

Похоже, элегантным решением для этого является линейное программирование . Я могу помочь с этим, если вы предоставите более подробную информацию.

только с 88 предметами, разбросанными по десяти слотам, грубое принуждение также не будет ужасным. Некоторая комбинация двух может быть самой легкой.

Обновление:

Основываясь на обновлении, которое вы дали, я думаю, что линейное программирование излишне (и его трудно применить). Я написал вам это довольно общее решение. Изучите это и поймите это. Если у кого-то есть исправления или улучшения, я бы хотел их услышать.

from itertools import ifilter, product

# Definition of ITEMS cut for brevity. See below.    

def find_best(slots, maximize, constraints):
    """example call:
         find_best(['helm', 'chest'], ['slashing', 'bludgeon'],
                   {'encumbrance': 12})
    """
    # save the slot names to construct a nice return value
    slot_names = slots

    # pull the actual lists of items for each slot out of the global dict
    slots = [ITEMS[slot] for slot in slots]

    # this function calculates the value of a solution
    value = lambda solution: sum(float(slot[attr]) for attr in maximize
                                                   for slot in solution)

    # replace our constraints with functions to check solutions
    constraints = [lambda solution:
                       sum(float(slot[attr]) for slot in solution) < constraint
                 for attr, limit in constraints.items()]

    # start with all possible combinations
    solutions = product(*slots)

    # chain together ifilters to weed out the ones that fail any of the
    # constraints. Note that for optimum performance, you should place the
    # constraints in descending order of probability to fail
    for constraint in constraints:
        solutions = ifilter(constraint, solutions)

    # We're going to do decorate, max, undecorate
    solutions = ((value(solution), solution) for solution in solutions)
    value, solution = max(solutions)

    # get the indexes and return
    return dict((name, slot.index(item)) for name, slot, item
                in zip(slot_names, slots, solution))

Обратите внимание, что вы должны хранить значения как числа с плавающей запятой, а , а не как строки! Проще (потому что это часто автоматически, когда вам это нужно) приводить к строке, чем к плавающей точке. Тогда вы можете убрать уродливые броски из моего кода. Мне просто было лень делать это для тебя. Обратите внимание, что вы можете вызывать столько ограничений, сколько хотите, но только один набор атрибутов для максимизации. Это имело смысл для меня. Если вы изучите код и поймете его, вы сможете изменить его по своему вкусу.

Вот как я изменил вашу структуру данных.

ITEMS = { 'helm': [{'Acid':' 2.71',
                        'Bludgeoning': '1.04',
                        'Cold': '2.71',
                        'Encumbrance': '8.00',
                        'Fire': '2.71',
                        'Holy': '2.71',
                        'Impact': '1.30',
                        'Lightning': '2.00',
                        'Name': 'Plate Helm',
                        'Piercing': '1.17',
                        'Slashing': '1.30',
                        'Unholy': '2.71'},

                       {'Acid': '2.18',
                        'Bludgeoning': '0.92',
                        'Cold': '2.18',
                        'Encumbrance': '7.00',
                        'Fire': '2.18',
                        'Holy': '2.18',
                        'Impact': '1.15',
                        'Lightning': '1.65',
                        'Name': 'Scale Helm',
                        'Piercing': '1.03',
                        'Slashing': '1.15',
                        'Unholy': '2.18'}],

              'chest':[{'Acid': '5.47',
                        'Bludgeoning': '2.05',
                        'Cold': '5.47',
                        'Encumbrance': '32.00',
                        'Fire': '5.47',
                        'Holy': '5.47',
                        'Impact': '2.57',
                        'Lightning': '4.06',
                        'Name': 'Plate Chest',
                        'Piercing': '2.31',
                        'Slashing': '2.57',
                        'Unholy': '5.47'},

                       {'Acid': '4.45',
                        'Bludgeoning': '1.84',
                        'Cold': '4.45',
                        'Encumbrance': '28.00',
                        'Fire': '4.45',
                        'Holy': '4.45',
                        'Impact': '2.30',
                        'Lightning': '3.31',
                        'Name': 'Scale Cuirass',
                        'Piercing': '2.07',
                        'Slashing': '2.30',
                        'Unholy': '4.45'}]}

обратите внимание, что значения внешнего словаря являются списками, а не кортежами, как вы сказали. Существует огромное различие!

1 голос
/ 05 сентября 2010

Похоже на вариант задачи Рюкзак . Динамическое программирование должно быть достаточно хорошим, если ваш лимит веса (статистика?) Не слишком велик.

Редактировать

Вот прототип в Java решения для динамического программирования:

public static int getBestAcidSum(int[][] acid, int[][] fire, int maxFire) {
    int slots = acid.length;
    int[][] acidSum = new int[slots][maxFire + 1];

    for (int j = 0; j < acid[0].length; j++)
        acidSum[0][fire[0][j]] = Math.max(acidSum[0][fire[0][j]], acid[0][j]);

    for (int i = 1; i < slots; i++)
        for (int j = 0; j < acid[i].length; j++)
            for (int fireSum = fire[i][j]; fireSum <= maxFire; fireSum++)
                if (acidSum[i-1][fireSum - fire[i][j]] > 0)
                    acidSum[i][fireSum] = Math.max(acidSum[i][fireSum], acidSum[i-1][fireSum - fire[i][j]] + acid[i][j]);

    int ret = 0;
    for (int fireSum = 0; fireSum <= maxFire; fireSum++)
        ret = Math.max(ret, acidSum[slots - 1][fireSum]);
    return ret;
}

public static void main(String[] args) {
    System.out.println(getBestAcidSum(new int[][] {{271, 218},  {547, 445}}, new int[][] {{271, 218}, {547, 445}}, 800));
}

Алгоритм: O (N * C), где N - общее количество элементов, а C - ограничение (в примере «maxFire»). Должно работать мгновенно с указанными вами величинами.

Код очень упрощен, но он сохраняет фундаментальную алгоритмическую сложность. Он возвращает только оптимальную сумму, которая удовлетворяет ограничениям. Должно быть легко изменено, чтобы вернуть фактическую комбинацию. Суммирование более чем одного показателя вместе должно быть легко включено. Значения были преобразованы в целые числа путем умножения на 100.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...