【问题标题】:Find sequences in list of ranges在范围列表中查找序列
【发布时间】:2020-08-05 15:51:06
【问题描述】:

我有一个包含范围信息的随机长度列表:

list = [
 [[7, 12], [6, 12], [38, 44], [25, 30], [25, 29]], 
 [[0, 5], [1, 5], [2, 5], [12, 16], [13, 16], [20, 23], [29, 33], [30, 33]], 
 [[5, 7], [6, 8], [7, 9], [8, 10], [9, 11], [10, 12], [16, 18], [17, 19], [18, 20], [23, 25], [24, 26], [25, 27], [26, 28], [27, 29], [33, 35], [34, 36], [35, 37], [36, 38], [37, 39], [38, 40], [39, 41], [40, 42], [41, 43], [42, 44]]
]

例如,第一个元素 [[7, 12], [6, 12], [38, 44], [25, 30]] 包含 4 个范围 7-12、6-12、38-44 和 25-30 等。

我需要找到长度给定列表长度的所有可能的链(链是连续范围的数组,其中第一个范围的结束 == 下一个范围的开始),因为我可以而且应该只从每个范围中取一个范围按行的确切顺序排列。

因此,对于此示例列表: 链条将是 [[6, 12], [12, 16], [16, 18]], [[7, 12], [12, 16], [16, 18]], [[25, 30], [30, 33], [33, 35]][[25, 29], [29, 33], [33, 35]]

现在我被困在处理超过三个长度的列表,无法提出递归解决方案。

【问题讨论】:

  • 你说你被卡住了,但你还没有分享你当前的代码。请发布您到目前为止所获得的信息。
  • @josh 我不认为列表可以展平,因为“应该只从每行中按行的确切顺序取一个范围”。
  • [[25, 30], [29, 33], [33, 35]] [25,30] 的结尾不等于 [29,33] 的开头
  • 如果你只有三个“行”,那么你最长的链只能是长度 3,对吗?
  • 此外,强烈建议不要使用 builtin 名称作为标识符名称 (list)。

标签: python algorithm numpy scipy


【解决方案1】:

您可以使用itertools.product 迭代所有可能的链(每个“行”中 1 个范围的所有组合),

然后通过一个检查特定链是否合法的简单函数过滤它们。

试试这个:

from itertools import product

def check_chain(chain):
    prev_end = chain[0][1]
    for start, end in chain[1:]:
        if start != prev_end:
            return False
        prev_end = end
    return True

all_candidate_chains = product(*list)

result = [[*chain] for chain in all_candidate_chains if check_chain(chain)]

print(result)

输出:

[[[7, 12], [12, 16], [16, 18]], [[6, 12], [12, 16], [16, 18]], [[25, 30], [30, 33], [33, 35]]]

编辑:

还可以使用zipallcheck_chain 替换为1-liner:

from itertools import product
result = [[*chain] for chain in product(*list) if all(end1 == start2 for (_, end1), (start2, _) in zip(chain, chain[1:]))]
print(result)

【讨论】:

  • 如果有人好奇:这里的时间复杂度是O(N x M^N),如果有N 行和M 列。
  • 是的,与立即修剪的“传统”深度优先搜索相比,我同意这对于大量输入来说效率低下。
  • 是的,这完全是蛮力的方法。有时让嵌套循环去 brrrrrrrrrrrrrr 就好了..... :P ericlippert.com/2020/03/27/new-grad-vs-senior-dev
【解决方案2】:

您可以在不查看所有排列的情况下执行此操作。从最后一项开始并制作一个字典,其中键是字典中的第一个值。然后向后遍历列表并根据添加到数组中的元组的第二个值查找上一个键:

最后,您将拥有一个字典,该字典键控到第一个列表的元组中的第一个值。此时您可以将值展平。

在这里,我在中间列表中添加了一对 [12,9],以便我可以看到它与分支路径一起使用:

from collections import defaultdict
from itertools import chain

l = [
 [[7, 12], [6, 12], [38, 44], [25, 30]], 
 [[0, 5], [1, 5], [2, 5], [12, 16], [12, 9],[13, 16], [20, 23], [29, 33], [30, 33]], 
 [[5, 7], [6, 8], [7, 9], [8, 10], [9, 11], [10, 12], [16, 18], [17, 19], [18, 20], [23, 25], [24, 26], [25, 27], [26, 28], [27, 29], [33, 35], [34, 36], [35, 37], [36, 38], [37, 39], [38, 40], [39, 41], [40, 42], [41, 43], [42, 44]]
]


d = defaultdict(list)
for k, v in l[-1]:
    d[k].append([[k,v]])

for sub in reversed(l[:-1]):
    ds = defaultdict(list)
    for k, v in sub:
        if v in d:
            ds[k].extend([[k,v], *v2] for v2 in d[v] )
    d = ds

list(chain.from_iterable(d.values()))

输出:

[[[7, 12], [12, 16], [16, 18]],
 [[7, 12], [12, 9], [9, 11]],
 [[6, 12], [12, 16], [16, 18]],
 [[6, 12], [12, 9], [9, 11]],
 [[25, 30], [30, 33], [33, 35]]]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-21
    • 2021-06-17
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 2020-05-19
    • 2015-07-22
    • 1970-01-01
    相关资源
    最近更新 更多