添加范围以计算重叠 python

时间:2021-05-25 17:20:48

标签: python performance range

我有一个范围列表。我现在想计算一个字典键:值,其中键是数字,值是数字存在的范围。

一个不好的计算方法是:

from collections import defaultdict
my_dict = defaultdict(int)
ranges = [range(-4200,4200), range(-420,420), range(-42,42), range(8,9), range(9,9), range(9,10)]

for singleRange in ranges:
    for number in singleRange:
        my_dict[number] += 1
sort_dict = sorted(my_dict.items(), key=lambda x: x[1], reverse=True)
print(sort_dict)

你如何更有效地做到这一点?

2 个答案:

答案 0 :(得分:2)

改进我之前的答案,该算法解决了 O(n + m) 中的问题,其中 n 是总范围的长度,m 是子范围的数量。

基本思想是只迭代一次 n 数字,保留当前数字所属范围数量的计数器。在每一步,我们检查我们是否已经通过了一个范围开始,在这种情况下计数器会增加。相反,如果我们已经通过了一个范围停止点,计数器就会递减。

下面的实际实现使用 numpypandas 来完成所有繁重的工作,因此算法的迭代性质可能看起来不清楚,但它基本上只是我所描述的矢量化版本。

与我之前回答的 600 毫秒相比,我的笔记本电脑上的 10k 范围下降到 20 毫秒。此外,内存使用量在这里也是 O(n + m),而那里是 O(nm),因此更大的 nm 成为可能。您可能应该使用此解决方案而不是第一个版本。

from collections import defaultdict

import numpy as np
import pandas as pd


# Generate data
def generate_ranges(n):
    boundaries = np.random.randint(-10_000, 10_000, size=(n, 2))
    boundaries.sort(axis=1)
    return [range(x, y) for x, y in boundaries]


ranges = generate_ranges(10_000)


# Extract boundaries
boundaries = np.array([[range.start, range.stop] for range in ranges])

# Add a +1 offset for range starts and -1 for range stops
offsets = np.array([1, -1])[None, :].repeat(boundaries.shape[0], axis=0)
boundaries = np.stack([boundaries, offsets], axis=-1)
boundaries = boundaries.reshape(-1, 2)

# Compute range counts at each crossing of a range boundary
df = pd.DataFrame(boundaries, columns=["n", "offset"])
df = df.sort_values("n")
df["count"] = df["offset"].cumsum()
df = df.groupby("n")["count"].max()

# Expand to all integers by joining and filling NaN
index = pd.RangeIndex(df.index[0], df.index[-1] + 1)
df = pd.DataFrame(index=index).join(df).fillna(method="ffill")

# Finally wrap the result in a defaultdict
d = defaultdict(int, df["count"].astype(int).to_dict())

答案 1 :(得分:1)

也许可以做一些更有效的事情,但这种解决方案的优势在于严重依赖 numpy 的速度。对于 10k 范围,这在我的笔记本电脑上运行约 600 毫秒。

from collections import defaultdict

import numpy as np


# Generate data
def generate_ranges(n):
    boundaries = np.random.randint(-10_000, 10_000, size=(n, 2))
    boundaries.sort(axis=1)
    return [range(x, y) for x, y in boundaries]


ranges = generate_ranges(10_000)


# Extract boundaries
starts, stops = np.array([[range.start, range.stop] for range in ranges]).T

# Set of all numbers we should test
n = np.arange(starts.min(), stops.max() + 1)[:, None]

# Test those numbers
counts = ((n >= starts[None, :]) & (n < stops[None, :])).sum(axis=1)

# Wrap the result into a dict
d = defaultdict(int, dict(zip(n.flatten(), counts)))