以大于 Python 列表中的值的最小差值对大多数数字进行采样的最快方法

Posted

技术标签:

【中文标题】以大于 Python 列表中的值的最小差值对大多数数字进行采样的最快方法【英文标题】:Fastest way to sample most numbers with minimum difference larger than a value from a Python list 【发布时间】:2021-09-03 10:56:48 【问题描述】:

给定一个包含 20 个浮点数的列表,我想找到一个最大的子集,其中任意两个候选者彼此不同,大于 mindiff = 1.。现在我正在使用蛮力方法使用itertools.combinations 从最大到最小的子集进行搜索。如下所示,代码在 4 秒后为 20 个数字的列表找到一个子集。

from itertools import combinations
import random
from time import time

mindiff = 1.
length = 20
random.seed(99)
lst = [random.uniform(1., 10.) for _ in range(length)]

t0 = time()
n = len(lst)
sample = []
found = False
while not found:
    # get all subsets with size n
    subsets = list(combinations(lst, n))
    # shuffle to ensure randomness
    random.shuffle(subsets)
    for subset in subsets:
        # sort the subset numbers
        ss = sorted(subset)
        # calculate the differences between every two adjacent numbers
        diffs = [j-i for i, j in zip(ss[:-1], ss[1:])]
        if min(diffs) > mindiff:
            sample = set(subset)
            found = True
            break
    # check subsets with size -1
    n -= 1

print(sample)
print(time()-t0)

输出:

2.3704888087015568, 4.365818049020534, 5.403474619948962, 6.518944556233767, 7.8388969285727015, 9.117993839791751
4.182451486587524

但是,实际上我有一个包含 200 个数字的列表,这对于暴力枚举是不可行的。我想要一种快速算法来仅采样一个 random 最大 子集,其最小差异大于 1。请注意,我希望每个样本都具有随机性和最大大小。有什么建议吗?

【问题讨论】:

对列表进行抽样并选择相差超过 1.0 的元素来构建集合? @rdas 是的。子集编号必须彼此不同,因此它必须是一个集合。但这并不重要。 看来已经发布的答案就足够了。它考虑到集合中的最小值将始终存在于最大子集中。因此,之后取每个大于 1 的值 @Onyambu 是的,现在我明白这已经足够了,但我完全忘记了我的目的是随机抽样!我只是在我的问题中强调了这一点。我的错。 【参考方案1】:

我之前的回答假设您只是想要一个最佳解决方案,而不是所有解决方案的统一随机样本。此答案假设您想要一个从所有此类最佳解决方案中均匀采样的解决方案。

    构造一个有向无环图G,其中每个点都有一个节点,当b - a > mindist时,节点ab相连。同时添加两个虚拟节点st,其中s -> x 代表所有xx -> t 代表所有x

    G 中的每个节点计算有多少个长度为k 的路径存在到t。您可以在O(n^2 k) 时间内使用动态编程和P[x][k] 表有效地完成此操作,首先填充P[x][0] = 0,除了P[t][0] = 1,然后填充P[x][k] = sum(P[y][k-1] for y in neighbors(x))

    继续这样做,直到达到最大值 k - 您现在知道最佳子集的大小。

    使用P 统一采样长度为kst 的路径来衡量您的选择。

    这是从s 开始的。然后我们查看s 的每个邻居并随机选择一个,其权重由P[s][k] 指定。这为我们提供了最优集合的第一个元素。

    然后我们重复执行此步骤。我们在x,查看x 的邻居并使用权重P[x][k-i] 随机选择一个,其中i 是我们所处的步骤。

    使用您在 3 中采样的节点作为您的随机子集。

上述在纯 Python 中的实现:

import random

def sample_mindist_subset(xs, mindist):
    # Construct directed graph G.
    n = len(xs)
    s = n; t = n + 1  # Two virtual nodes, source and sink.
    neighbors = 
        i: [t] + [j for j in range(n) if xs[j] - xs[i] > mindist]
        for i in range(n)
    neighbors[s] = [t] + list(range(n))
    neighbors[t] = []

    # Compute number of paths P[x][k] from x to t of length k.
    P = [[0 for _ in range(n+2)] for _ in range(n+2)]
    P[t][0] = 1
    for k in range(1, n+2):
        for x in range(n+2):
            P[x][k] = sum(P[y][k-1] for y in neighbors[x])

    # Sample maximum length path uniformly at random.
    maxk = max(k for k in range(n+2) if P[s][k] > 0)
    path = [s]
    while path[-1] != t:
        candidates = neighbors[path[-1]]
        weights = [P[cn][maxk-len(path)] for cn in candidates]
        path.append(random.choices(candidates, weights)[0])

    return [xs[i] for i in path[1:-1]]

请注意,如果您想多次从同一组数字中采样,您不必每次都重新计算P,并且可以重复使用它。

【讨论】:

@Shaun Han “我试图了解这两个虚拟节点的用途。”虚拟节点的目的是您不知道子集的第一个或最后一个元素是什么,但是通过添加两个连接到所有事物的虚拟节点,您知道最大路径长度正好是两倍长,并且任何最大长度路径总是以s 开始,以t 结束。你可以看到它好像我暂时将-infinf 添加到输入列表中,所以我总是知道我从哪里开始/结束,并在最后再次剥离它们。 我想我现在有点明白了。这个解决方案很棒。我在我的 200 号码列表上进行了测试,但它总是生成相同的子集。我不知道是不是因为我的列表只有一个解决方案。测试时是否有可以生成不同子集的列表(或种子)? @ShaunHan 例如sample_mindist_subset(np.linspace(0, 10, 20), 1)。顺便说一下P[s][maxk]是解的总数。 这实际上是一个非常巧妙的解决方案,感谢@orlp【参考方案2】:

我可能不完全理解这个问题,因为现在解决方案非常简单。编辑:是的,毕竟我误解了,OP 不只是想要一个最佳解决方案,而是希望从一组最佳解决方案中随机抽样。这个答案并没有错,但它也是对与 OP 感兴趣的不同问题的答案。


简单地对数字进行排序并贪婪地构造子集:

def mindist_subset(xs, mindist):
    result = []
    for x in sorted(xs):
        if not result or x - result[-1] > mindist:
            result.append(x)
    return result

正确性证明草图。

假设我们有一个解决方案S给定输入数组A,它具有最佳大小。如果它不包含min(A),请注意我们可以从S 中删除min(S) 并添加min(A),因为这只会增加min(S)S 中第二小的数字之间的距离。结论:我们可以不失一般性地假设min(A) 是最优解的一部分。

现在我们可以递归地应用这个参数。我们将min(A) 添加到解决方案中,并删除所有与min(A) 太接近的元素,留下剩余元素A'。然后我们会遇到一个子问题,其中应用完全相同的参数,我们可以选择min(A') 作为解决方案的下一个元素,等等。

【讨论】:

这不能保证最大的子集。 @ShaunHan 我敢肯定。 @fthomson mindist_subset([2.6092208852795107, 2.8006790011745086, 4.447608238289605], 1) 根据需要返回 [2.6092208852795107, 4.447608238289605] @ShaunHan 你能证明这是不正确的吗?因为这是解决方案实际上是正确的 @ShaunHan 如果您希望从所有最优子集中随机均匀抽样,您的问题会变得更加有趣,这是我对您的问题的一种可能解释,当时我说“我可能不完全理解问题”。

以上是关于以大于 Python 列表中的值的最小差值对大多数数字进行采样的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

检查列表是不是包含大于 C# 中的值的项目 [关闭]

在 Python 中查找最小值或最大值的值的索引

Python-根据范围转换列表中的值

解析函数加窗子句

如何用matlab筛选一个数组中大于某值的数

MySQL 查询时间差值大于某一个值的 记录