Можно ли использовать лямбда-питоны для изменения внутренней работы другой функции? - PullRequest
0 голосов
/ 01 октября 2018

У меня есть следующий фрагмент кода, который мне нужно (массово) ускорить.Как это, это чрезвычайно неэффективно.

possible_combos.append([comb for comb in
    itertools.combinations_with_replacement(input_list, target_number_of_packages)
        if np.sum([j[1] for j in comb])==target_number_of_packages])

В разобранном виде:

possible_combos 

- это вывод

input_list

- это список кортежей в форме ([...], number_of_packages)

target_number_of_packages

- количество пакетов, которое мне нужно получить.Я могу объединить столько элементов списка «input_list», сколько захочу (повторы разрешены), но при добавлении их «number_of_packages» нужно точно достичь target_number_of_packages, иначе комбинация недопустима.

Я думало чем-то вроде

possible_combos.append([comb for comb in
    itertools.combinations_with_replacement(input_list, lambda x:x=...)))

, но не смог заполнить пробел.

Мой вопрос: можно ли вообще использовать лямбду таким образом?Мне не нужен ответ о том, как обращаться с этим конкретным вариантом использования, потому что я решил его по-другому (с продуктом itertools и функцией рекурсивного генератора), но я все же хотел бы знать, было бы решение?

Вкратце: Можно ли использовать лямбду для изменения значения внутри другой функции на лету ?

Минимальный пример проблемы:

input_list=[1,2,3,4,5,6] #in minmal form

target_number_of_packages=4

possible_combos should be [[1,1,1,1],[2,1,1],[2,2],[3,1],[4]]

И я ищу что-то примерно эквивалентное, но более быстрое, чем

possible_combos=[comb for comb in
    itertools.combinations_with_replacement(input_list) if np.sum(comb)==target_number_of_packages]

Просто с целью np.sum (comb) ==, вставленной в itertools.combination_with_replacement- если вообще возможно.

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

Ответы [ 3 ]

0 голосов
/ 07 октября 2018

Использование itertools.combination_with_replacement создает аналогичный список

from itertools import combinations_with_replacement

input_list = [1,2,3,4,5,6] 

l = [list(combination_with_replacement(input_list, i)) for i in range(5)]
res = [list(filter(lambda x: sum(x) == 4, i)) for i in l]
# [[], [(4,)], [(1, 3), (2, 2)], [(1, 1, 2)], [(1, 1, 1, 1)]]
0 голосов
/ 07 октября 2018

lambda - это просто синтаксис , что позволяет вам создать функциональный объект в выражении (def functionname(..): ... - это оператор , и вы не можете использовать операторы внутри выражения).Так что лямбда просто создает функциональный объект, и ничего особенного.Вы не можете использовать функции для изменения локального пространства имен другой функции во время выполнения, нет.

Из комментариев выясняется, что вы спрашиваете, как можно использовать обратный вызов , чтобы, возможно, решить вашу проблему.проблема, но я не думаю, что вы еще полностью понимаете свое проблемное пространство и то, как работают такие вещи, как list.sort(key=...).Реализация сортировки в Python явно вызывает обратный вызов key, по выбору вызываемая функция передает информацию, а функция сортировки изменяет поведение в зависимости от того, что возвращается;функция ключа не может выбрать, что произойдет с возвращаемым значением.

По моему мнению, здесь вы задаете не тот вопрос.

Проблема, которую вы пытаетесь решить, подмножество Проблема с ранцем ;у вас есть неограниченный вариант, так как Я могу объединить столько элементов списка «input_list», сколько я хочу (повторы разрешены) .

Попытка использовать itertools решить это неправильный подход;itertools функции будут генерировать много неправильных комбинаций.По сути, вы генерируете все комбинации с повторениями ( мультимножеств * ) для выходных размеров от 1 до целевого размера, поэтому вы можете рассчитать количество таких мультимножеств для каждого заданного размера и суммировать их:

def multiset_size(n, k):
    return int(factorial(k + n - 1) / (factorial(k) * factorial(n - 1)))

generated = sum(multiset_size(len(input_list), i + 1) for i in range(target_number_of_packages))

Для вашего игрушечного примера, с 6 входами и целевым размером 4, вы генерируете 209 различных комбинаций, но есть только 5 жизнеспособных комбинаций.Это колоссальные 40,8 комбинаций, потраченных впустую за жизнеспособный выход!При больших входных размерах это соотношение будет только (намного) ухудшаться.

Проблема полного ранца обычно решается с использованием динамического программирования подход .Программирование chrestomathy site Rossettacode.org имеет отличный пример на Python о том, как использовать DP для решения неограниченного ранца .По сути, вы сохраняете таблицу «мешков» для каждого уровня емкости (от нуля до максимума) и обновляете эту таблицу, как видите, если добавление текущего элемента в мешки, в которых есть место для элемента, приводит к получению лучшего (более ценного) мешкапока лучшая комбинация для этого мешка.

Но при создании всех возможных комбинаций вам лучше использовать свой собственный итеративный или рекурсивный подход.Здесь нет готовой функции itertools, которую вы можете использовать, и использование функции обратного вызова не облегчит эту задачу.

Если взять лист из решения DP, итеративное решение будет использовать серию складывает мешков для каждой возможной общей емкости и копирует их в следующую кучу, когда вы добавляете больше таких предметов в каждый мешок, в котором есть место для них:

from itertools import repeat

def unbounded_knapsack_combos(input_list, target):
    # collect piles of filled sacks, here each pile contains
    # all sacks of the same capacity.
    # A sacks consist of counts for each item in input_list, so
    # sack[0] counts how many input_list[0] items were used.
    # piles start empty, except for pile 0 (the empty sack pile)
    piles = [[] for _ in range(0, target)]
    piles[0] = [[0] * len(input_list)]

    # process from smallest to biggest so we can avoid some work, like
    # adding an item of size target - 1 on a pile that will never combine
    # with larger items
    size_idx = [(s, i) for i, (_, s) in enumerate(input_list) if s <= target]
    for size, i in sorted(size_idx):
        for s in range(size, target + 1 - size):
            for sack in piles[s - size]:
                new_sack = sack[:]
                new_sack[i] += 1
                piles[s].append(new_sack)

        # Yield knapsacks that can be completed
        for sack in piles[target - size]:
            new_sack = sack[:]
            new_sack[i] += 1
            # turn the item counts in each sack in the target pile back into
            # items from the input list
            yield [item for i, c in enumerate(new_sack) if c
                   for item in repeat(input_list[i], c)]

        # remove piles that we no longer need; any pile that won't
        # fit larger items (multiple items can have the same size)
        del piles[target - size + 1:]

Это решение используетitertools.repeat(), но только потому, что удобно производить окончательный вывод из счетчиков в мешках.

Для вашего игрушечного примера, используя тот же формат (value, size), типичный для проблемы с рюкзаком, это дает:

>>> input_list = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6)]
>>> target_size = 4
>>> solution = unbounded_knapsack_combos(input_list, target_size)
>>> [[s for _, s in sack] for sack in solution]
[[1, 1, 1, 1], [1, 1, 2], [2, 2], [1, 3], [4]]

Это только когда-либо генерирует фактических жизнеспособных комбинаций и ничего более.

Функция отличается от полного решения тем, что здесь all сохраняются возможные комбинации, а не только одна комбинация с наилучшим значением для комбинированных предметов.И поскольку мы генерируем все комбинации, нет необходимости сортировать элементы по их соотношению значений, как это делает подход связанного DP (сортировка помогает избежать копирования слишком большого количества менее оптимальных решений в цикле).

Рекурсивная версия произведенная schwobaseggl по сути делает то же самое;создайте мешки для данной емкости, а рекурсивные вызовы добавляют больше элементов для следующей большей емкости.Мой просто оказывается итеративным, так что вы не столкнетесь с рекурсивными пределами Python, и он выдает результаты так, как находит их (как itertools).Рекурсивная версия также вынуждена многократно объединять списки (операция O (N ^ 2) для N == глубины рекурсии), поэтому итеративный подход намного быстрее.

0 голосов
/ 05 октября 2018

Генерация всех возможных комбинаций и отфильтровывание комбинаций с подходящей суммой делает слишком много работы.Но вы можете написать свою собственную функцию, которая генерирует именно то, что вам нужно:

def combos(lst, total, max=None):
    if total < 0:
        return
    elif total == 0:
        yield []
    for x in lst:
        if max is None or x <= max:
            for com in combos(lst, total - x, x):
                yield [x] + com

>>> list(combos([1, 2, 3, 4, 5, 6], 4))
[[1, 1, 1, 1], [2, 1, 1], [2, 2], [3, 1], [4]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...