【问题标题】:list of lists of which the sum is within a given range (homework)总和在给定范围内的列表列表(作业)
【发布时间】:2019-12-17 11:11:19
【问题描述】:

我想定义一个接受以下参数的函数: - 一个数字列表 - 最低限度 - 最大值

它应该返回一个列表(或集合)列表,其中每个列表的总和在最小值和最大值之间。列表中不应有重复项。

我所拥有的并没有返回所有可能的列表:

def givelists(List, minimum, maximum):
    currentlist = [] #list to keep track of sum of currently included subset of list
    listoflist = [] #list of all lists of which the sum is between specified minumum and maximum
    for number in List:
        if minimum < (sum(currentlist) + number) < maximum:
            currentlist.append(number)
            listoflist.append(currentlist)
            currentlist = []
        elif (sum(currentlist) + number) < minimum:
            currentlist.append(number)
        else:
            if number in range(minimum, maximum):
                listoflist.append(number)
    return(listoflist)

例如:

givelists(list(range(1,6)), 2, 8)

# Output: [[1, 2], [3], [4], [5]]

【问题讨论】:

  • 你期待什么结果?使用range(1,2), 1, 1 对其进行测试。你会期待什么?
  • 对于您示例的输出,我会说 [ [3], [4], [5], [1,2], [1,3], [1,4], [1,5], [2,3]..... ] 这是总和在 2 到 8 之间的所有可能组合。那么你能重新制定预期的输出吗?
  • 请不要通过破坏您的帖子为他人增加工作量。通过在 Stack Exchange 网络上发帖,您已在 CC BY-SA 4.0 license 下授予 Stack Exchange 分发该内容的不可撤销的权利(即无论您未来的选择如何)。根据 Stack Exchange 政策,帖子的非破坏版本是分发的版本。因此,任何破坏行为都将被撤销。如果您想了解更多关于删除帖子的信息,请参阅:How does deleting work?

标签: python algorithm list combinations


【解决方案1】:

试试这个:

from itertools import chain, combinations

def powerset_sum(iterable, minimum, maximum):
    s = set(iterable)
    combs = chain.from_iterable(combinations(s,r) for r in range(len(s)+1))
    valid_combs = [list(comb) for comb in combs if (minimum < sum(comb) < maximum)]
    return valid_combs
print(powerset_sum(list(range(1,6)), 2, 8))

输出:

[[3],
 [4],
 [5],
 [1, 2],
 [1, 3],
 [1, 4],
 [1, 5],
 [2, 3],
 [2, 4],
 [2, 5],
 [3, 4],
 [1, 2, 3],
 [1, 2, 4]]

【讨论】:

  • 非常感谢。然而,这里的缺点是它的计算量很大,输入列表越大,最小-最大间隔也越大。
【解决方案2】:

你需要考虑列表中数字的多种组合,比如;

for every number in the list:
    test if that number is a feasible solution
    for every other number in the list:
        test if they can be grouped with that number \
        and produce a feasible solution # (recursion here)

所以主要是你的代码应该是这样的

def sums_in_range(lst, min, max):
    result = []
    for i, number in enumerate(lst):
        if min < number < max:
            result.append([number])
        candidates = sums_in_range(
            [test for test in lst[i+1:] if test < max-number], min-number, max-number)
        for candidate in candidates:
            result.append([number]+candidate)
    return result

print(sums_in_range(sorted(list(range(1, 6))), 2, 8))

注意事项

  • 我在lst[i+1:] 中使用i+1,因此它不会返回带有重复项的列表,例如[1, 1, 1]
  • 我正在做lst[i+1:] 所以它不会计算像[1, 2][2, 1] 这样的重复项
  • sorted 考虑负值,请参阅 cmets

编辑

为了完整起见(我有一些空闲时间),如果你想更进一步

我已经对此处发布的 3 个答案进行了比较,内存和时间明智的数据相同

你可以找到重现结果的代码here

这就是我想出的结果

测试输入是

test_lst = list(range(1, 25))
test_min = 2
test_max = 50

【讨论】:

  • 条件test &lt; max - number 假定数字为非负数;例如,sums_in_range([2, 3, -3], 1, 3) 返回 [[2]],缺少相同总和的 [2, 3, -3]
  • @kaya3 没错,我没有针对负值对其进行测试,所以感谢您的注意,test &lt; max - number 是一种减少递归分支数量的技巧,但是 sorting 传入列表解决了问题。
【解决方案3】:

这是一个不使用导入模块的递归解决方案:

def get_lists_in_range(lst, minimum, maximum):
    result = []
    for i, item in enumerate(lst):
        #print(i, item, lst[i+1:])
        all_lists = get_lists_in_range(lst[i+1:], minimum, maximum)
        if minimum <= item <= maximum:
            result.append([item])
        for l in all_lists:
            if minimum <= item + sum(l) <= maximum:
                result.append([item] + l)           
    return result

print(get_lists_in_range(list(range(1, 6)), 2, 8))

【讨论】:

  • 好吧,我迟到了 10 分钟 :)
  • 它不会返回所有列表。
  • 你是对的,有一个错误。我想我摆脱了它。尽管如此,Mahmoud 表明这种解决方案效率不高:我不会减少递归中的最小值/最大值,并且总是必须仔细检查项目和新列表的总和是否在界限内。
【解决方案4】:

这是一个递归生成器函数,它执行backtracking search,并进行了一些优化。首先对列表进行排序,计算每个索引后的正负数之和;这些可以用作可以用剩余元素得出的总和的界限。这为包含负数的列表提供了正确答案。作为对列表排序的副作用,子集按字典顺序查找。

对于组合问题,生成器函数通常比在列表中返回所有结果更有用,因为可能存在大量解决方案,而且您通常不需要一次将它们全部存储在内存中。但是,yieldappend 慢一点,所以如果您总是希望结果显示在列表中,我已经展示了使用 cmets 进行的适当更改。

def subsets_sum_in_range(lst, min_s, max_s):
    lst = sorted(lst)
    n = len(lst)
    pos_sums = [0] * (n + 1)
    neg_sums = [0] * (n + 1)
    for i in reversed(range(n)):
        pos_sums[i] = pos_sums[i+1] + max(0, lst[i])
        neg_sums[i] = neg_sums[i+1] + min(0, lst[i])

    def helper(i, s, t):
        if min_s <= s <= max_s:
            yield t
            # out.append(t)
        for j in range(i, n):
            if s + pos_sums[j] < min_s or s + neg_sums[j] > max_s:
                break
            v = lst[j]
            yield from helper(j + 1, s + v, t + (v,))
            # helper(j + 1, s + v, t + (v,))

    return helper(0, 0, ())
    # out = []
    # helper(0, 0, ())
    # return out

例子:

>>> list(subsets_of_sum([1, 2, 3, 4, 5], 3, 6))
[(1, 2),
 (1, 2, 3),
 (1, 3),
 (1, 4),
 (1, 5),
 (2, 3),
 (2, 4),
 (3,),
 (4,),
 (5,)]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-26
    • 2016-10-15
    • 1970-01-01
    • 2021-12-30
    • 2013-11-07
    • 1970-01-01
    相关资源
    最近更新 更多