Python программирование - определите правильную комбинацию статей для чтения, чтобы достичь максимальной интеллектуальной ценности - PullRequest
0 голосов
/ 28 января 2020

Недавно столкнулся с проблемой программирования, которая выглядит следующим образом.

Набор документов, которые можно прочитать, скажем, 4. Количество страниц - 4, 4, 6, 8 соответственно. Интеллектуальная ценность в каждом из этих статей 2, 4, 4, 5 соответственно. Можно только прочитать максимум 15 страниц. Какие статьи вы бы хотели прочитать, чтобы максимизировать интеллектуальную ценность?

Python Решение, которое я придумал, приведено ниже. Если я увеличу article_count до 20, программа займет гораздо больше времени. Можете ли вы помочь предложить способы его оптимизации? 1. более эффективное использование памяти 2. исключение комбинаций и, следовательно, меньшее количество итераций 3. улучшения алгоритма / структуры данных

from memory_profiler import profile
from itertools import combinations
import numpy as np

@profile
def max_iv(articles_count, pages, ivs, pages_read_limit):
    max_iv = 0
    for lgth in range(1,len(pages)+1):
        reading_options = combinations(range(len(pages)),lgth)
        for ro in reading_options:
            if sum((pages[x] for x in ro)) <= pages_read_limit:
                iv = sum((ivs[x] for x in ro))
                if iv > max_iv:
                    max_iv = iv
    print(max_iv)

if __name__ == '__main__':
    articles_count = 4
    pages = np.random.randint(1,10,articles_count)
    ivs=np.random.randint(1,10,articles_count)
    pages_read_limit = 15
    max_iv(articles_count, pages, ivs, pages_read_limit)

Ответы [ 2 ]

0 голосов
/ 28 января 2020

Это интересная проблема, хотя на самом деле это не вопрос StackOverflow, но я не смог устоять:).

Ниже приведено решение, которое дает мое собственное решение, и простое решение методом грубой силы (для проверки результаты), а также время, необходимое для 10 000 выполнений каждого и вашего решения:

from random import randint
from collections import namedtuple
from itertools import combinations
from timeit import timeit


Article = namedtuple('Article', ['id', 'pages', 'value'])


def get_articles(min_pages, max_pages, min_value, max_value, number_of_articles):
    return [Article(n, randint(min_pages, max_pages), randint(min_value, max_value)) for n in range(number_of_articles)]


def _sort_articles_by_relative_value(articles):
    return list(reversed(sorted(articles, key=lambda a: a.value / a.pages)))


def _pick_articles_recursive(articles, pages_read_limit, n=0, picked=None, pages=0, value=0):
    if picked is None:
        picked = []
    while n < len(articles):
        if pages + articles[n].pages == pages_read_limit:
            return pages_read_limit, value + articles[n].value, picked + [articles[n]]
        elif pages + articles[n].pages < pages_read_limit:
            inclusive = _pick_articles_recursive(
                articles, pages_read_limit, n+1,
                picked + [articles[n]], pages + articles[n].pages, value + articles[n].value)
            if inclusive[0] == pages_read_limit:
                return inclusive
            else:
                exclusive = _pick_articles_recursive(
                    articles, pages_read_limit, n+1,
                    picked, pages, value)
                if inclusive[1] > exclusive[1]:
                    return inclusive
                else:
                    return exclusive
        n += 1
    return pages, value, picked


def pick_articles(articles, pages_read_limit):
    articles = _sort_articles_by_relative_value(articles)
    return _pick_articles_recursive(articles, pages_read_limit)


def pick_articles_brute(articles, pages_read_limit):
    bv = 0
    bp = []
    for n in range(len(articles)+1):
        for picked_articles in combinations(articles, n):
            nv = sum([a.value for a in picked_articles])
            np = sum([a.pages for a in picked_articles])
            if nv > bv and np <= pages_read_limit:
                bp = picked_articles
                bv = nv
    return sum([a.pages for a in bp]), bv, bp


def max_iv(pages, ivs, pages_read_limit):
    max_iv = 0
    for lgth in range(1,len(pages)+1):
        reading_options = combinations(range(len(pages)),lgth)
        for ro in reading_options:
            if sum((pages[x] for x in ro)) <= pages_read_limit:
                iv = sum((ivs[x] for x in ro))
                if iv > max_iv:
                    max_iv = iv


def main():
    articles = get_articles(min_pages=1, max_pages=5, min_value=1, max_value=5, number_of_articles=6)

    result_new = pick_articles(articles, pages_read_limit=10)
    result_brute = pick_articles_brute(articles, pages_read_limit=10)

    print('articles :', articles)
    print('result   :', result_new)
    print('brute    :', result_brute)

    print('t result :', timeit(lambda: pick_articles(articles, pages_read_limit=10), number=10000))
    print('t brute  :', timeit(lambda: pick_articles_brute(articles, pages_read_limit=10), number=10000))
    print('t max_iv :', timeit(lambda: max_iv(
        pages=[a.pages for a in articles], ivs=[a.value for a in articles], pages_read_limit=10), number=10000))


main()

Типичный результат:

articles : [Article(id=0, pages=1, value=5), Article(id=1, pages=2, value=2), Article(id=2, pages=3, value=4), Article(id=3, pages=4, value=3), Article(id=4, pages=5, value=3), Article(id=5, pages=2, value=4)]
result   : (10, 16, [Article(id=0, pages=1, value=5), Article(id=5, pages=2, value=4), Article(id=2, pages=3, value=4), Article(id=3, pages=4, value=3)])
brute    : (10, 16, (Article(id=0, pages=1, value=5), Article(id=2, pages=3, value=4), Article(id=3, pages=4, value=3), Article(id=5, pages=2, value=4)))
t result : 0.0626382
t brute  : 0.6400916000000001
t max_iv : 0.5818494999999999

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

Наконец, обратите внимание, что _sort_articles_by_relative_value и _pick_articles_recursive вместе мы дадим вам ответ, остальная часть кода предназначена для сравнения и создания привлекательного вида.

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

0 голосов
/ 28 января 2020

Я векторизовал немного вашего кода и ускорил 30% в моих локальных тестах, но не смог избавиться от части комбинаций. Это похоже на вопрос об алгоритмах высокого уровня - удачи!

def max_iv(articles_count, pages, ivs, pages_read_limit):
    max_iv = 0
    n_pages_range = np.arange(len(pages))
    for lgth in range(1,len(pages)+1):

        reading_options = np.array(list(combinations(n_pages_range, lgth)))
        pages_arr = pages[reading_options].sum(axis=-1)
        iv_arr = ivs[reading_options].sum(axis=-1)
        iv_arr = iv_arr[pages_arr < pages_read_limit]

        if iv_arr.size != 0:        
            curr_iv = iv_arr.max()
            if curr_iv > max_iv:
                max_iv = curr_iv
    return max_iv
...