【问题标题】:Search a list or array for best match to a sublist (by numerical difference)?在列表或数组中搜索与子列表的最佳匹配(通过数值差异)?
【发布时间】:2020-05-02 13:25:34
【问题描述】:

我有一个这样的值列表:

[ 669,  592,  664, 1005,  699,  401,  646,  472,  598,  681, 1126, ...]

我的问题是,给定一小部分数字,例如:

[ 400, 650, 475 ]

如何获取最接近子列表的子列表,例如:

[401,  646,  472, ]  # difference = 1 + 4 + 3 = 8

我开始考虑在堆树中组织所有长度的子列表,但后来我意识到我很可笑,肯定有人以前遇到过这个问题,但我找不到任何东西。你认为我需要这样做吗?

有没有办法在列表中搜索匹配(或最接近)搜索条件的最佳子列表(连续)?

【问题讨论】:

  • “但后来我意识到我很可笑”哈哈。我有时也会这样做。
  • 我认为这不一定是荒谬的。另一种选择是进行两次通过的方法。首先通过计算长度的绝对差 n 子列表来创建一个新列表。第二遍您可以浏览新列表并找到最小值。如果你想要多个子列表,你可以使用字典。
  • 您可以使用 sum() 并检查结果。我会尝试在这里创建一些东西。
  • 您确定只想要最匹配的子列表吗?不一定是子列表的排序列表或 n 最佳或类似的东西?
  • @kaya3 我的直觉完全正确,但我对可搜索列表或搜索项列表的长度没有上限,因此我必须为每个可能的大小制作一个堆树或其他东西。这就是为什么我想,我会在这里问这个问题。也许如果我发现一般情况属于特定范围,我可以对这些东西进行优化。身份证。我在下面有一些很好的答案就足够了。

标签: python arrays list search data-structures


【解决方案1】:

快速 NumPy 解决方案:

import numpy as np

# Taken from https://rigtorp.se/2011/01/01/rolling-statistics-numpy.html
def rolling_window(a, window):
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

lst = np.array([669, 592, 664, 1005, 699, 401, 646, 472, 598, 681, 1126])
small = np.array([400, 650, 475])

sublists = rolling_window(lst, len(small))

# Only find the best match
match = sublists[np.abs(sublists - small).sum(axis=1).argmin()]
print(match)

# Array of matches, best to worst
match = sublists[np.abs(sublists - small).sum(axis=1).argsort()]
print(match)

时间安排:

In [1]: import numpy as np
   ...:
   ...: # Taken from https://rigtorp.se/2011/01/01/rolling-statistics-numpy.html
   ...: def rolling_window(a, window):
   ...:     shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
   ...:     strides = a.strides + (a.strides[-1],)
   ...:     return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
   ...:
   ...: lst = np.array([669, 592, 664, 1005, 699, 401, 646, 472, 598, 681, 1126])
   ...: small = np.array([400, 650, 475])
   ...:
   ...: sublists = rolling_window(lst, len(small))

In [2]: %timeit sublists[np.abs(sublists - small).sum(axis=1).argmin()]
6.43 µs ± 51.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [3]: %timeit sublists[np.abs(sublists - small).sum(axis=1).argsort()]
10 µs ± 1.03 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: lst = [ 669,  592,  664, 1005,  699,  401,  646,  472,  598,  681, 1126 ]
   ...: small = [ 400, 650, 475 ]

In [5]: %timeit min((lst[i:i + len(small)] for i in range(len(lst) - len(small) + 1)), key=lambda sub: sum(abs(a - b) for a, b in zip(small, sub)))
17.4 µs ± 1.39 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [6]: %timeit sorted((lst[i:i + len(small)] for i in range(len(lst) - len(small) + 1)), key=lambda sub: sum(abs(a - b) for a, b in zip(small, sub)))
15.9 µs ± 208 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

如果列表很长,我预计速度会更快。

【讨论】:

    【解决方案2】:

    您可以遍历较长列表的子列表并将关键函数传递给min

    lst = [ 669,  592,  664, 1005,  699,  401,  646,  472,  598,  681, 1126 ]
    small = [ 400, 650, 475 ]
    match = min((lst[i:i + len(small)] for i in range(len(lst) - len(small) + 1)), key=lambda sub: sum(abs(a - b) for a, b in zip(small, sub)))
    print(match) # 401,  646,  472
    

    如果您想要一个最佳匹配到最差匹配的排序列表,只需将min 替换为sorted

    print(sorted((lst[i:i + len(small)] for i in range(len(lst) - len(small) + 1)), key=lambda sub: sum(abs(a - b) for a, b in zip(small, sub))))
    [[401, 646, 472], [472, 598, 681], [669, 592, 664], [646, 472, 598], [699, 401, 646], [1005, 699, 401], [592, 664, 1005], [664, 1005, 699], [598, 681, 1126]]
    

    如果你觉得单行字很可怕(我必须承认我确实有,哈哈),这里有一个更易读的版本:

    lst = [ 669,  592,  664, 1005,  699,  401,  646,  472,  598,  681, 1126 ]
    small = [ 400, 650, 475 ]
    
    def key(sublist):
        result = 0
        for a, b in zip(sublist, small):
            result += abs(a - b)
        return result
    
    sublist_iter = (lst[i:i + len(small)] for i in range(len(lst) - len(small) + 1))
    match = min(sublist_iter, key=key) # or `sorted`
    print(match)
    

    【讨论】:

    • 就我个人而言,我会把它分解一下,给出子列表迭代器,也许还有分数函数名称,这样会更清楚发生了什么,但这是我正在写的答案好吧。
    【解决方案3】:

    你可以这样做:

    import operator
    numbers = [ 669,  592,  664, 1005,  699,  401,  646,  472,  598,  681, 1126, 669,  592,  664]
    
    search =  [ 400, 650, 475 ]
    
    diffs = {}
    
    for index in range(len(numbers)-len(search)+1):
        subset = numbers[index:index+len(search)]
        diff = sum([abs(x-y) for x,y in zip(subset,search)])
        diffs[diff] = diffs.get(diff,[]) + [subset]
    
    for key, value in sorted(diffs.items(), key=operator.itemgetter(0)):
        print("{} : {}".format(key, value))
    

    输出:

    8 : [[401, 646, 472]]
    330 : [[472, 598, 681]]
    516 : [[669, 592, 664], [669, 592, 664]]
    547 : [[646, 472, 598]]
    719 : [[699, 401, 646]]
    728 : [[1005, 699, 401]]
    736 : [[592, 664, 1005]]
    843 : [[664, 1005, 699]]
    862 : [[1126, 669, 592]]
    880 : [[598, 681, 1126]]
    951 : [[681, 1126, 669]]
    

    它是按顺序打印的,如果你有两次相同的值,它会打印两个列表来表示这个差异。

    【讨论】:

    • 你可以通过使用operator.itemgetter(0) 作为你的key 而不是lambda 来获得一个小的优化 - 我记得几年前我上次研究它时它的速度大约提高了 10% .
    • 这看起来与 iz_ 提供的答案基本相同,只是您的答案更清楚地说明了正在发生的事情。我说的对吗?
    • @LegitStack 几乎相同。在他编辑之前,他只有最好的匹配,这就是为什么我也提交我的。在我的解决方案中,您有两个优点:您可以将差异作为键,并且如果两个列表相等(例如在 516 中),您可以在我的解决方案中看到它,但在 von iz_ 中看不到
    • @PeterDeGlopper 感谢您的意见。我记得我读过一些东西,但我忘记了,我改变了我的解决方案。
    • 嗯,这会构建一个中间数据结构 (diffs),因此它会占用更多内存并完成更多工作。如果您对列表列表的结果字典有使用,这很好,但如果您只想要最接近的匹配或最好的 n,我会坚持使用其他版本。 (sort 文档说,每个元素只对 key 函数进行一次评估,因此您不会通过保留自己的内部值记录来保存评估。)
    【解决方案4】:

    很惊讶没有人提供numpy 解决方案:

    import numpy as np
    
    numbers = np.array([669, 592, 664, 1005, 699, 401, 646, 472, 598, 681, 1126])
    searchs = [400, 650, 475]
    
    for search in searchs:
        result = numbers[np.abs(numbers - search).argmin()]
        print(result)
    

    输出:

    401
    646
    472
    

    【讨论】:

    • 哇,这是一个简单的实现!那么,您认为这比公认的答案(列表理解方法)更快吗?
    • 让我运行一些测试。好奇自己,tbh。
    • 肯定更快;不幸的是,这似乎不正确。列表中最接近的数字应该是连续的,但这个答案没有考虑到这一点。它会寻找最接近较小列表中每个元素的数字,在这种情况下它们恰好是连续的。
    • 他想要最近的子列表。例如,如果您使用numbers = np.array([669, 592, 664, 1005, 699, 401, 646, 472, 598, 681, 1126,647]),则输出将为401 647 472,但这不是列表中的有效子列表(3 个彼此相邻的数字),而是列表中的 3 个最小的数字。
    • 不完全确定你在问什么,但我对这个问题的理解是在numberslen(searchs) 元素彼此相邻)中寻找最“相似”(最小元素差异)到searchs。例如,如果您要将400 附加到numbers,您的解决方案将打印400 646 472(三个不是numbers 子列表的数字)。
    猜你喜欢
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多