Почему это решение работает в Javascript, но не работает в Python? (Динамическое программирование)

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

* Напишите функцию с именем canSum(targetSum, numbers), которая возвращает True только в том случае, если числа в массиве могут суммироваться с целевой суммой. Все числа в массиве — положительные целые числа, и вы можете использовать их более одного раза для решения.

Пример:

canSum(7, [2, 4]) -> False потому что нельзя составить 7, складывая 2 и 4. *

Мое решение грубой силы было следующим:

def canSum(targetSum, numbers):
    if targetSum == 0:
        return True
    if targetSum < 0:
        return False

    for n in numbers:
        remainder = targetSum - n
        if canSum(remainder, numbers):
            return True

    return False


print(canSum(7, [2, 3])) # True
print(canSum(7, [5, 3, 4, 7])) # True
print(canSum(7, [2, 4])) # False
print(canSum(8, [2, 3, 5])) # True

Работает хорошо, но было бы быстрее, если бы мы запоминали решения остатков (это объясняется на минуте 1:28:03 в видео). Я сделал следующее с Python, что и делает инструктор, но он возвращает только True, и я не могу понять, почему...

def canSum(targetSum, numbers, memo = {}):
    if targetSum in memo:
        return memo[targetSum]
    if targetSum == 0:
        return True
    if targetSum < 0:
        return False

    for n in numbers:
        remainder = targetSum - n
        if canSum(remainder, numbers, memo):
            memo[targetSum] = True
            return True

    memo[targetSum] = False
    return False


print(canSum(7, [2, 3]))
print(canSum(7, [5, 3, 4, 7]))
print(canSum(7, [2, 4]))
print(canSum(8, [2, 3, 5])) # All of them return True

Потрясающе, большое спасибо!

martin 23.12.2020 19:23

Я как раз собирался прокомментировать ваш новый вопрос о питоне, что ваш код на самом деле не работает должным образом...

Nick 31.12.2020 01:12
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
6
3
862
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Благодаря статье, которой поделился @Jared Smith, я смог это понять.

Проблема вызвана тем, как python обрабатывает аргументы по умолчанию. Из статьи:

В Python при передаче изменяемого значения в качестве аргумента по умолчанию в функцию аргумент по умолчанию изменяется каждый раз, когда изменяется это значение.

Мой memo словарь менялся при каждом звонке. Поэтому я просто изменил memo=None и добавил проверку, был ли это первый вызов функции:

def canSum(targetSum, numbers, memo=None):
    if memo == None:
        memo = {}

    if targetSum in memo:
        return memo[targetSum]
    if targetSum == 0:
        return True
    if targetSum < 0:
        return False

    for n in numbers:
        remainder = targetSum - n
        if canSum(remainder, numbers, memo):
            memo[targetSum] = True
            return True

    memo[targetSum] = False
    return False


print(canSum(7, [2, 3])) # True
print(canSum(7, [5, 3, 4, 7])) # True
print(canSum(7, [2, 4])) # False
print(canSum(8, [2, 3, 5])) # True
print(canSum(3000, [7, 14])) # False -> Works fast with large inputs!

В вашем механизме сохранения есть ошибка.

Когда вы вызываете функцию в первый раз:

print(canSum(7, [2, 3]))

Этот вызов вернет true и создаст ключ в словаре со значением true( 7 : true).
Это причина, почему это не работает

Теперь проверим третий вызов:

print(canSum(7, [2, 4]))

Первое, что делает ваша функция, это проверяет, есть ли число 7 в словаре:

if targetSum in memo:
    return memo[targetSum]

И поскольку с 1-го вызова у вас уже есть номер 7 в словаре, он будет искать его и возвращать его значение - и с 1-го вызова значение для номера 7 равно True

Это словарь до и после первого вызова.

{}                                    # before first call
{1: False, 3: True, 5: True, 7: True} # after first call

После первого вызова этот словарь будет возвращать True каждый раз, когда вы вызываете функцию с номером 7.
И поскольку Python использует общий аргумент по умолчанию (это объясняется в комментарии Джареда Смита), каждый вызов будет возвращать True для числа 7.

Как это исправить? Вы должны сохранить как targetSum, так и nums в словаре и проверить оба значения.

Есть два способа сделать это:

1-й способ: Инкапсулируйте targetSum и nums в кортеж и используйте этот кортеж в качестве ключа. Вот как будет выглядеть этот дикт

{
  (7, (2, 3)) : True,
  (7, (5, 3, 4, 7)) : True,
  (7, (2, 4)) : False
}

Это реализация этого:

keyTuple = (targetSum, tuple(numbers))
if keyTuple in memo:
    return memo[keyTuple]

if targetSum == 0:
    return True
if targetSum < 0:
    return False

for n in numbers:
    remainder = targetSum - n
    if canSum(remainder, numbers, memo):
        memo[keyTuple] = True
        return True

memo[keyTuple] = False
return False

Вы также должны преобразовать list в tuple , потому что python не позволяет использовать списки в качестве ключей для словарей.

Второй способ - использовать словарь словарей. Что-то вроде этого.

{
 7: {
   (2,3): True,
   (5, 3, 4, 7): True
   (2,4): False
 }

}

Это реализация:

def canSum(targetSum, numbers, memo = {}):
numbersTuple = tuple(numbers)

if targetSum in memo:
    targetDict = memo[targetSum]
    if numbersTuple in targetDict:
        return targetDict[numbersTuple]
else:
    memo[targetSum] = {}

if targetSum == 0:
    return True
if targetSum < 0:
    return False

for n in numbers:
    remainder = targetSum - n
    if canSum(remainder, numbers, memo):
        targetDict = memo[targetSum]
        targetDict[numbersTuple] = True
        return True

targetDict = memo[targetSum]
targetDict[numbersTuple] = False
return False

Если вы чего-то не понимаете, напишите мне комментарий :)

Я думаю, что более простым решением будет изменить memo на None и добавить в начале простую проверку, как описано в моем решении.

martin 23.12.2020 19:34

@MartínSchere, я попробовал ваше решение и нашел проблему. Ваше решение всегда возвращает True, когда я использую dict для кэширования результатов. Попробуйте сами - создайте переменную cahce, содержащую словарь, и передайте ее при каждом вызове в качестве аргумента memo. Вам нужна функция, которая может кэшировать результаты, но ваше решение не может этого сделать по той же причине - оно кэширует только targetSum, а не список чисел.

user14781663 23.12.2020 19:38

@MartínSchere действительно, проверьте, может ли он кэшировать значения

user14781663 23.12.2020 19:45

Другие вопросы по теме