Все комбинации, которые равны или превышают заданное число

Это отличается от проблемы с разменом монет .

Учитывая x=[a:1,b:2,c:1,d:3,e:2], мне нужно рассчитать все возможные комбинации ключей, чтобы сумма значений, связанных с этими ключами, просто превышала или равнялась заданному значению, скажем, 5. Простое превышение 5 означает, что если выбранные элементы меньше 5 , мы можем добавить еще один элемент, даже если он превышает 5, но как только мы превысим или равны 5, больше нельзя будет добавить элементы.

Есть две проблемы, которые усложняют мой подход:

  1. Обратите внимание, что некоторые значения повторяются, например. 1 и 2 встречаются дважды, и их нужно рассматривать отдельно.
  2. Порядок в данном случае имеет значение.

Порядок тоже имеет значение, поэтому все [b,c,d], [b,d,c], [c,b,d], [c,d,b], [d,b,c], [d,c,b] включены отдельно. В идеале порядок имеет значение только тогда, когда мы превышаем 5, и пока сумма ровно 5, порядок не имеет значения.

Таким образом, некоторые из решений вышеуказанного вопроса: [a,b,c,d] с суммой 7, [a,b,c,e] с суммой 6, [b,c,d] с суммой 6, [d,e] с суммой 5 и т. д.

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

РЕДАКТИРОВАТЬ 1. Лучший способ сформулировать вопрос может состоять в том, чтобы найти все комбинации, такие, что сумма (i) равна 5 или (ii) чуть превышает 5 в качестве суммы до тех пор, пока предпоследний элемент не будет меньше 5 и, следовательно, добавление последнего элемента сделало его больше 5. Как только мы получим 5 или ›5, никакие дополнительные элементы добавляться не будут.

РЕДАКТИРОВАНИЕ 2: разъяснение проблемы с заказом. Чтобы дать немного контекста, ключи — это устройства, а значения — это необходимые ресурсы. И общее количество доступных ресурсов равно 5. И устройствам выделяются ресурсы в соответствии с их ключами, то есть первая пара ключ-значение будет обслуживаться первой (отсортирована по ключу в алфавитном порядке). Для b требуется 2, но, скажем, доступен только 1 ресурс — ему будет назначен 1, а оставшийся 1 будет назначен при следующем запуске (переход). Поэтому, когда сумма равна ровно 5, порядок не имеет значения, поскольку каждый получает то, что хотел, и нет никакого распространения на следующий слот. Например. ed и de означают, что оба получают то, что хотели, поэтому они оба должны быть включены. Но для списков, которые превышают 5 только из-за добавления последнего элемента, порядок имеет значение, поскольку переполнение произойдет только для последнего элемента. Например. dce означает, что d и c получают 3 и 1 соответственно, а e получает только 1 (при этом 1 выливается в следующий запуск). Точно так же ced означает, что c и d получают 1 и 3 соответственно, при этом для d происходит перелив, поскольку ему присваивается только 1.


person user3656142    schedule 19.02.2021    source источник
comment
Вам нужно, чтобы это было правильно, или быстро и правильно?   -  person btilly    schedule 19.02.2021
comment
@btilly Я хочу понять, как подойти к этой проблеме, поэтому сначала она должна быть правильной. Как только у меня будет рабочий код, я постараюсь понять, как его можно улучшить.   -  person user3656142    schedule 19.02.2021
comment
Хорошо, во-первых, это двусмысленно. Иногда хочется порядка. Иногда нет. Различные перестановки одного и того же решения будут большинством ваших примеров. И будет безумное количество решений. Можете ли вы немного уточнить эту спецификацию и попытаться уменьшить число?   -  person btilly    schedule 20.02.2021
comment
Я считаю, что идеальный порядок имеет значение только тогда, когда раздел довольно странный. Нам действительно не все равно между "bed" и "ebd"? В обоих случаях мы превышаем 5 на последнем символе. Если все порядки имеют значение, то это разумно, хотя и большой результат, но это странно.   -  person Scott Sauyet    schedule 20.02.2021
comment
@btilly Я добавил подробности, разъясняющие проблему заказа. Я согласен, что это будет огромное количество.   -  person user3656142    schedule 20.02.2021
comment
@user3656142 user3656142 Тогда я предлагаю вам использовать только пары комбинаций, где первая комбинация — это все элементы, которые не могут отображаться последними, а вторая — все, что может отображаться последними. Если сумма получается равной, то первый список пуст.   -  person btilly    schedule 20.02.2021


Ответы (2)


Обновленный ответ

В вопрос добавлены дополнительные сведения о требованиях. Я думаю, что эта версия соответствует им. Но ожидаемая структура вывода не была указана, так что это предположение. Это заканчивается массивом результатов, подобных этому:

{initial: {a: 1, b: 2, c: 1}, last: {d: 3}}

или вот так:

{initial: {b: 2, c: 1, e: 2}}

Он делает это, сначала находя все подмножества начальных значений вместе с их дополнениями, используя функцию partitions. partitions (['a', 'b', 'c', 'd']) будет иметь шестнадцать элементов, включая [['a', 'b', 'd'], ['c']], [['b', 'd'], ['a', 'c']] и [[], ['a', 'b', 'c', 'd']].

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

Вот реализация в Javascript:

// utility functions
const last = (xs) => 
  xs [xs .length - 1]

const partitions = (xs) => 
  xs .length === 0
    ? [[[], []]]
    : partitions (xs .slice (0, -1)) .flatMap (([p, q]) => [ 
        [[...p, last (xs)], q], 
        [p, [...q, last (xs)]] 
      ])

// helper function
const total = (xs) =>
  xs .reduce ((a, [k, v]) => a + v, 0)


// main function
const sumTo = (n, xs, parts = partitions (xs)) =>
  parts .flatMap (([incl, excl], _, __, t = total (incl)) => [
    ... (t == n ? [{initial: incl}] : []),
    ... (t < n
           ? excl .filter (([k, v]) => v + t > n) 
                  .map (e => ({initial: incl, last: [e]}))
           : []
        )
  ])


// public function
const subsetSum = (n, dict) =>
  sumTo (n, Object.entries (dict))
    .map (({initial, last}) => ({
      initial: Object .fromEntries (initial), 
      ... (last ? {last: Object .fromEntries (last)} : {})
    }))


// sample data
const dict = {a: 1, b: 2, c: 1, d: 3, e: 2}


// demo
console .log (subsetSum (5, dict))
.as-console-wrapper {max-height: 100% !important; top: 0}

Начнем с тривиальной вспомогательной функции last, которая просто выбирает последний элемент массива. Далее имеем partition, уже описанный выше. Наша основная функция — sumTo, которая выполняет описанную работу. Его входными данными являются целевое число и структура, подобная [['a', '1], ['b', 2'], ['c', 1], ['d', 3], ['e', 2]], которую легко получить из словаря, но с которой проще работать. Он возвращает объект с двумя свойствами, например {initial: [['a', 1], ['b', 2], ['c', 1]], last: [['d', 3]]}. Наконец, у нас есть общедоступная функция subsetSum, которая преобразует формат словаря во входные данные, необходимые для sumTo, а затем преобразует результат обратно в выходной формат. Это было бы легко исправить, чтобы создать любой формат вывода, который вам нужен.

Немного отформатировав вывод, мы можем увидеть наши результаты более просто:

subsetSum  (5, dict)
  .map (({initial, last}) => 
      Object .keys (initial) .join ('') +
      (last ? '-' + Object .keys (last) .join ('') : '')
  )
//=> ["abc-d", "abc-e", "abe", "ab-d", "acd", "ace-b", 
//    "ace-d", "ad-b", "ad-e", "ae-d", "bce", "bc-d", 
//    "bd", "be-d", "cd-b", "cd-e", "ce-d", "de"]

Это, вероятно, неэффективно. У меня нет реального представления о вероятных закономерностях роста проблемы. Но этот алгоритм экспоненциален по размеру словаря, и для этого метода нет другого пути, поскольку проблема подмножества, лежащая в основе partitions, дает 2 ^ n результатов.

Но это может быть отправной точкой для сравнения с другими решениями.

Оригинальный ответ

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

if (n <= 0) or if our list of entries is empty,
  return a list containing only the empty string
otherwise return the concatenation of the following lists, one for each entry:
  a recursive call passing 
    - `n - value`
    - the list excluding the current entry,
  with each result being prepended with the current entry's key 

Если вы знакомы с синтаксисом Javascript, то это должно работать:

const excluding = (i) => (xs) =>
  [...xs .slice (0, i), ...xs .slice (i + 1)]

const sumTo = (n, xs) =>
  n <= 0 || xs.length == 0 
    ? ['']
    : xs .flatMap (
        ([k, v], i) => sumTo (n - v, excluding (i) (xs)) .map (r => k + r)
      )
  
const subsetSum = (n, dict) => 
  sumTo (n, Object .entries (dict))

const dict = {a: 1, b: 2, c: 1, d: 3, e: 2}

console .log (subsetSum (5, dict))
.as-console-wrapper {max-height: 100% !important; top: 0}

Трудно представить, что порядок может быть совершенно неуместным, поскольку 'cde' должно подойти, а 'dec' — нет. Но я не понимаю идею о том, что порядок имеет значение только тогда, когда общая сумма превышает цель. Можете ли вы объяснить более полно?

person Scott Sauyet    schedule 19.02.2021
comment
Я добавил детали, разъясняющие проблему заказа. - person user3656142; 20.02.2021
comment
Обновлено новым решением, которое, я думаю, соответствует вашим требованиям. - person Scott Sauyet; 20.02.2021

Я думаю, что изменение алгоритма обмена монет, подобное этому, решает проблему.

import itertools
def subset_sum(numbers, target, partial=[]):
    exceed = sum([ v for k, v in partial[:-1]])
    s = sum([ v for k, v in partial])
    if exceed >= target:
        return
    elif s == target: 
        print (partial,sum([ v for k, v in partial]), target)
    elif s > target:
        print("print all permutation")
        p = list(itertools.permutations(partial))
        for state in p:
            legit = sum([v for k, v in state[:-1]])
            if (legit < target):
                print(state)

    for i in range(len(numbers)):
        k, v = numbers[i]
        remaining = numbers[i+1:]
        subset_sum(remaining, target, partial + [(k,v)])

if __name__ == "__main__":
    subset_sum([('a',1),('b',2),('c',1),('d',3),('e',2)],5)
person hatef Alipoor    schedule 19.02.2021
comment
Один из результатов print all permutation (('a', 1), ('b', 2), ('c', 1), ('d', 3)) (('a', 1), ('b', 2), ('d', 3), ('c', 1)). Здесь первое правильно, так как до c у нас есть сумма 4, поэтому мы добавляем d, и получается сумма › 5, и мы останавливаемся. Но для второго у нас есть 6 после добавления d, поэтому c после этого добавлять не следует. - person user3656142; 19.02.2021
comment
@user3656142 user3656142 спасибо за ваш отзыв, я обновил его, надеюсь, это поможет - person hatef Alipoor; 19.02.2021