【问题标题】:Find all minimal elements in a list or set as weighted by a function查找列表中的所有最小元素或按函数加权的集合
【发布时间】:2014-08-11 00:07:26
【问题描述】:

如果我想找到列表的 a 最小值或集合 x 由该集合上的某个函数 f 给出,我可以使用方便的单行器,例如

min(x,key=f)

(4.91 微秒)

虽然对于“纯”min 函数,在大多数情况下返回多个元素是没有意义的(因为它们都是相同的,而对于集合只有一个),如果您选择最小值根据某些函数,您通常会想知道它最小的所有元素。

换句话说,我正在寻找一个简短、简洁和快速的函数,它允许我根据一些加权函数返回所有最小元素,它最好适用于列表和集合(并在输入的数据类型)。

对于列表,我写得最快的是

def allmin(x,f):
    vals = map(f, x)
    minval = min(vals)
    return [x[i] for i,e in enumerate(vals) if e==minval]

6.73 微秒

但是,这远非最佳,并且不适用于集合。首先,在映射时,所有函数值在某个时间点都在内存中,因此这是确定最小值而不是再次查看最小值的最佳时间,这可以说明这已经慢了 50% 虽然没有额外的与单分钟示例相比,必须执行计算(列表构造除外)。与我设法编写的集合相比,唯一可比的东西是

def allmin(x,f):
    vals = [(f(e), e) for e in x]
    minval = min(vals)[0]
    return {e for val,e in vals if val==minval}

8.44 µs(列表版本的列表理解为 7.29 µs)

我有什么方法可以让列表的性能达到更好的 allmin 列表版本的性能,最重要的是,接近 min(x,key=f) 的性能?

(为了说明和时间安排,我假设

f = lambda x: (x-4.5)**2
x = random.choice([[0,1,2,3,4,5,6,7,8,9,10,11,13],{0,1,2,3,4,5,6,7,8,9,10,11,13}])

)

【问题讨论】:

    标签: python list python-2.7 set min


    【解决方案1】:

    如果您不知道最小值的数量,那么一个简单的单程方法是为目前看到的最低权重保留一个最小值的运行列表:

    def minimal(iterable, func):
        'Return a list of minimal values according to a weighting function'
        it = iter(iterable)
        try:
            x = next(it)
        except StopIteration:
            return []
        lowest_values = [x]
        lowest_weight = func(x)
        for x in it:
            weight = func(x)
            if weight == lowest_weight:
                lowest_values.append(x)
            elif weight < lowest_weight:
                lowest_values = []
                lowest_weight = weight
        return lowest_values
    

    它在行动:

    >>> s = {'abc', 'defg', 'hij', 'kl', 'mno', 'qr', 'stuv', 'wx', 'yz'}
    >>> minimal(s, len)
    ['qr', 'kl', 'yz', 'wx']
    

    或者,如果您事先知道有多少个最小值,heapq.nsmallest 函数将直接有效地解决问题。对于 n 值中的 k 个最小值,它会 n 调用您的加权函数并使用与 k 成比例的内存(即它的缓存效率很高):

    >>> from heapq import nsmallest
    >>> s = {'abc', 'defg', 'hij', 'kl', 'mno', 'qr', 'stuv', 'wx', 'yz'}
    >>> nsmallest(4, s, key=len)
    ['qr', 'kl', 'yz', 'wx']
    

    【讨论】:

    • 呃,这和我最终的算法基本一样。首先比较 weight > minimum_weight 然后如果满意则继续进行比较将导致进一步加速,因为平均而言,更多元素可能会达到条件。在您当前的代码中,如果 weight > minimum_weight,则执行两个不必要的比较。
    • 呃,两个人可以得出相似的答案——本质上只是对 min() 的代码进行了微小的修改。另外,我没有看到任何提及 heapq.nsmallest()。无论如何,我认为我们已经用尽了这个简单的小问题的有趣解决方案空间。
    【解决方案2】:

    您当前花费 Θ(n) 将 f() 应用于所有元素,然后另一个 Θ(n) 来找到其中的最小值,最后另一个 Θ(n) 来找到等于最小值的所有元素.简而言之,您花费了 3 x Θ(n),其中n 是输入列表的大小。

    理论上,您可以在 2 x Θ(n) 中执行此操作,方法是在应用 f() 时花费 Θ(n) 来找到最小值,然后再花费 Θ(n) 来检索所有最小元素。但是,似乎有一种更快的方法,您花费 Θ(n) 来应用 f() 并找到最小值,但只在检索所有最小元素时花费 O(n)。 (请注意,在最坏的情况下,O(n) 与 Θ(n) 没有什么不同。对于下面的算法,当列表中的所有元素都相同或列表以相反的顺序排序时,就会发生这种最坏情况。 )

    def allmin(x,f):
        minVal = 9999999999999999999999999
        mapped = []
        for a in x:
            mapVal = f(a)
            if mapVal <= minVal:
                minVal = mapVal
                mapped.append((a, mapVal))
        return [a for (a,m) in mapped if m == minVal]
    

    我自己的时间测量结果表明,对于从 0 到 100 的整数列表,与您的 allmin() 方法相比,时间改进了大约 20%。

    对于非常大的输入列表,可能值得从几个元素开始采样,这样您就可以为 minVal 提供更好的初始值(而不是对非常大的值进行简单的初始化)。

    ========================================== 编辑===== =====================================

    这是一个可进一步提高 5~10% 速度的版本。加速来自观察,一旦找到新的最小值,所有以前存储的映射值都可以被丢弃。因此,不再需要用于检索最小值的最终 O(n),整个算法需要 1 x Θ(n) 才能运行。

    def newallmin(x,f):
        minVal = f(x[-1])
        minList = []
        for a in x:
            mapVal = f(a)
            if mapVal > minVal:
                continue
            if mapVal < minVal:
                minVal = mapVal
                minList = [a]
            else: # mapVal == minVal
                minList.append(a)
        return minList
    

    我一直在使用大小为 10,000,000 的列表执行时间测量,所有元素的范围从 0 到 100。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-16
      • 2019-04-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-22
      • 2020-04-12
      相关资源
      最近更新 更多