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

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

В качестве примера, вот что у меня есть.

List of inputs: [18.01, 42.01, 132.04, 162.05, 203.08, 176.03]

Target value: 1800.71

Я хочу найти все возможные комбинации перечисленных входов, чья сумма находится в пределах 0,5 от 1800,71. Таким образом, сумма может быть где угодно между 1800.21 и 1801.21.

Я уже знаю, что два входа могут быть:

[18.01, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05, 162.05] **which gives a sum of 1800.59**

И

[18.01, 18.01, 203.08, 203.08, 203.08, 162.05, 203.08, 18.01, 18.01, 18.01, 18.01, 18.01, 18.01, 18.01, 18.01, 18.01, 18.01, 42.01, 162.05, 203.08, 203.08] **which gives a sum 1800.71**

Я НЕ ищу комбинацию, которая максимально приближает меня к целевому значению; Меня интересуют ВСЕ возможные комбинации, которые находятся в пределах 0,5 от целевого значения.

Если бы кто-нибудь мог помочь мне с этой проблемой, я был бы очень признателен!

5
Alan 28 Июн 2019 в 00:23

2 ответа

Еще один ответ в том же духе, что и существующие прекрасные ответы. Я обнаружил, что проще использовать диапазон вместо целевого значения + допуска и использовать тривиальное (неоптимизированное) рекурсивное решение, которое кажется достаточно быстрым, чтобы найти ~ 1000 ответов для вашего варианта использования.

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

def fuzzy_coins(vals, lower, upper):
    '''
    vals: [Positive]
    lower: Positive
    upper: Positive
    return: [[Int]]
    Returns a list of coefficients for vals such that the dot
    product of vals and return falls between lower and upper.
    '''
    ret = []
    if not vals:
        if lower <= 0 <= upper:
            ret.append(())
    else:
        val = vals[-1]
        for i in xrange(int(upper / val) + 1):
            for sub in fuzzy_coins(vals[:-1], lower, upper):
                ret.append(sub + (i,))
            lower -= val
            upper -= val
    return ret

Тем не менее, это занимает ~ 100 мс в Python 2.7 и 3.6

[('1800.33', (0, 2, 1, 0, 0, 9)),
 ('1800.35', (3, 0, 0, 1, 0, 9)),
 ('1800.35', (5, 3, 0, 0, 0, 9)),
 ('1800.38', (0, 10, 0, 2, 0, 6)),
 ('1800.38', (1, 11, 2, 0, 0, 6)),
...
 ('1800.92', (86, 6, 0, 0, 0, 0)),
 ('1800.94', (88, 2, 1, 0, 0, 0)),
 ('1800.96', (91, 0, 0, 1, 0, 0)),
 ('1800.96', (93, 3, 0, 0, 0, 0)),
 ('1801.00', (100, 0, 0, 0, 0, 0))]
Took 0.10885s to get 988 results

Например использование:

from __future__ import print_function
import pprint
import time


def main():
    vals = [18.01, 42.01, 132.04, 162.05, 203.08, 176.03]
    target = 1800.71
    fuzz = .5

    lower = target - fuzz
    upper = target + fuzz
    start = time.time()
    coefs = fuzzy_coins(vals, lower, upper)
    end = time.time()
    pprint.pprint(sorted(
        ('%.2f' % sum(c * v for c, v in zip(coef, vals)), coef)
        for coef in coefs
    ))
    print('Took %.5fs to get %d results' % (end - start, len(coefs)))
4
Cireo 28 Июн 2019 в 04:52

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

lst = [18.01, 42.01, 132.04, 162.05, 203.08, 176.03]
target = 1800.71

def find_combination(lst, target, current_values=[], curr_index=0, threshold=0.5):
    s = sum(current_values)

    if abs(s - target) <= threshold:
        yield s, tuple(current_values)

    elif s - target < 0:
        for i in range(curr_index, len(lst)):
            yield from find_combination(lst, target, current_values + [lst[i]], i)

    elif s - target > 0:
        curr_index += 1
        if curr_index > len(lst) - 1:
            return

        yield from find_combination(lst, target, current_values[:-1] + [lst[curr_index]], curr_index)

out = []
for v in find_combination(sorted(lst, reverse=True), target):
    out.append(v)

out = [*set(out)]

print('Number of combinations: {}'.format(len(out)))

## to print the output:
# for (s, c) in sorted(out, key=lambda k: k[1]):
#   print(s, c)

Печать :

Number of combinations: 988

РЕДАКТИРОВАТЬ: отфильтрованы дубликаты.

3
Andrej Kesely 27 Июн 2019 в 22:39