【问题标题】:Slice List of floats by value in Python在 Python 中按值对浮点数进行切片列表
【发布时间】:2015-01-17 14:57:01
【问题描述】:

我有一个包含数千个浮点数的列表,我希望能够按最小值和最大值对其进行切片。

例如使用:

flist = [1.9842, 9.8713, 5.4325, 7.6855, 2.3493, 3.3333]

(我的实际列表是 400,000 个浮点数,但上面是一个工作示例)

我想要类似的东西

def listclamp(minn, maxn, nlist):

这样

print listclamp(3, 8, flist)

应该给我

[3.3333, 5.4325, 7.6855]

我还需要这样做 10,000 到 30,000 次,所以速度很重要。

(到目前为止,我没有尝试过的示例代码,因为这对我来说是新的 python 领域)

【问题讨论】:

  • 过滤项目,然后对它们进行排序,反之亦然。虽然首先过滤会更好,因为它会减少 N 中的 NlogN
  • 您有任何代码可以显示您尝试过的内容吗?
  • 我尝试执行此操作的第一种方法花费不到 1 微秒,因此执行数千次仍将花费一小部分时间。那么速度真的算吗?
  • 您的列表中是否包含“数千个浮点数”,或者只是他给出的示例?你还切片“数千次”吗?
  • 您的数据范围是多少?您的值是在整个范围内均匀分布,还是集群中的数据?最后你有多少有效数字?

标签: python list python-2.7 slice clamp


【解决方案1】:

显而易见的做法是先排序然后过滤,或者先过滤再排序。

如果你每次都有相同的列表,那么首先排序显然是一个胜利,因为你只需要排序一次而不是每次。这也意味着您可以使用二进制搜索而不是线性遍历进行过滤(如 ventsyv's answer 中所述——尽管除非您的列表比这个长得多,否则这可能不会有回报。

如果您每次都有不同的列表,那么首先过滤可能是一个胜利,因为排序可能是缓慢的部分,并且您正在对较小的列表进行排序。

但是让我们停止猜测并开始测试。

使用包含数千个浮点数的列表,其中大约一半在范围内:

In [1591]: flist = [random.random()*10 for _ in range(5000)]
In [1592]: %timeit sorted(x for x in flist if 3 <= x < 8)
100 loops, best of 3: 3.12 ms per loop
In [1593]: %timeit [x for x in sorted(flist) if 3 <= x < 8]
100 loops, best of 3: 4 ms per loop
In [1594]: %timeit l=sorted(flist); l[bisect.bisect_left(l, 3):bisect.bisect_right(l, 8)]
100 loops, best of 3: 3.36 ms per loop

所以,过滤然后排序获胜; ventsyn 的算法确实弥补了部分差异,但不是全部。但是当然,如​​果我们只有一个列表要排序,那么排序一次而不是数千次是明显的胜利:

In [1596]: l = sorted(flist)
In [1597]: %timeit l[bisect.bisect_left(l, 3):bisect.bisect_right(l, 8)]
10000 loops, best of 3: 29.2 µs per loop

因此,如果您一遍又一遍地拥有相同的列表,显然要对其进行一次排序。

否则,您可以测试您的真实数据……但我们正在谈论将需要几毫秒的时间减少高达 22% 的费用。即使您执行了数千次,也可以为您节省不到一秒钟的时间。只是输入不同实现的成本——更不用说理解、概括、调试和性能测试——远不止于此。


但实际上,如果您要在数十万个值上执行数百万次操作,并且速度很重要,那么您首先不应该使用列表,而应该使用NumPy 数组。 NumPy 可以只存储原始的float 值,而不会将它们装箱为 Python 对象。除了节省内存(和改善缓存局部性)之外,这意味着np.sort 中的内部循环比sorted 中的内部循环更快,因为它不必进行最终涉及拆箱的 Python 函数调用两个数字,直接比较就好了。

假设您首先将值存储在数组中,它是如何叠加的?

In [1607]: flist = np.random.random(5000) * 10
In [1608]: %timeit a = np.sort(flist); a = a[3 <= a]; a = a[a < 8]
1000 loops, best of 3: 742 µs per loop
In [1611]: %timeit c = b[3 <= b]; d = c[c < 8]
10000 loops, best of 3: 29.8 µs per loop

因此,对于“不同列表”的情况,它比过滤和排序快大约 4 倍,即使使用笨拙的算法(我一直在寻找可以塞进 %timeit 行的东西,而不是最快或最可读……)。对于“一遍又一遍的相同列表”的情况,它几乎与二分法一样快,即使 没有 平分(当然你也可以使用 NumPy 平分)。

【讨论】:

    【解决方案2】:

    这将返回您想要的排序列表:

    flist = [1.9842, 9.8713, 5.4325, 7.6855, 2.3493, 3.3333]
    
    def listclamp(minn, maxn, nlist): 
        return sorted(filter(lambda x: xminn <= x <= maxn, nlist))
    
    print listclamp(3, 8, flist) 
    

    一种更快的方法,使用list comprehensions

    def listclamp2(minn, maxn, nlist): 
        return sorted([x for x in flist if (minn <= and x<=maxn)])
    
    print listclamp2(3, 8, flist)
    

    请注意,根据您的数据,最好先过滤列表然后对其进行排序(就像我在上面的代码中所做的那样)。

    有关性能的更多信息,请参阅this link

    【讨论】:

    • 只需使用minn &lt;= x &lt;= maxn。转换为 float 是不必要的(如果您要一遍又一遍地进行转换,这很浪费),并且使用两个单独的比较而不是链式比较,可读性较差且速度较慢。 (此外,如果您必须构建一个 lamdba 只是为了使用 filter,我认为理解将比 filter 调用更具可读性和更快,但这是一个判断调用,需要使用真实数据进行测试,分别。)
    • 同意列表理解几乎总是比 filter + lambda 好
    • 您已经取消了float 调用,但您仍在使用单独的比较而不是链式比较。 (在理解版本中是一个列表而不是生成器表达式,但我认为这不会有太大的不同。)
    【解决方案3】:

    对列表进行排序(如果你反复使用同一个列表,只排序一次),然后使用二分查找查找上下界的位置。 想想看,有一个包可以 - bisect。

    【讨论】:

    • 二分搜索用对数搜索替换了线性过滤器,这很好……但这也意味着您必须对整个列表进行排序,而不是对较小的过滤列表进行排序,这很容易使您付出比获得更多的成本.
    • 我对此表示怀疑。他说他必须对列表进行数千次切片,对列表进行一次排序可能最终会更快。
    • 我假设他需要对数以千计的不同列表执行此操作。你是对的,如果它总是相同的列表,他应该只排序一次。
    • 另外,您必须对结果切片进行排序,如果切片很大,则对整个数组或仅对切片进行排序可能没有太大区别。但在一般情况下你是对的,如果你没有对同一个数组进行足够多次的分片,排序就会太昂贵。
    • 事实证明(至少通过快速测试),即使每次使用不同的列表,在 sort-then-binsearch 比 filter-then- 慢得多之前,您也必须得到非常窄的切片种类。有关我对他真实数据的最佳猜测的数字,请参阅我的(更新的)答案。 (不过,过滤和排序肯定比排序和二元过滤更简单,因为我们谈论的是节省时间,所以我会选择更简单的……)
    猜你喜欢
    • 1970-01-01
    • 2017-05-31
    • 2020-08-27
    • 1970-01-01
    • 1970-01-01
    • 2015-05-21
    • 2012-05-21
    • 2018-11-25
    相关资源
    最近更新 更多