使用字典将唯一字母(第二个值)映射到每个字母的最小值,然后只需将该字典中的 [value, key] 对作为输出:
minimi = {}
inf = float('inf')
for val, key in my_list:
# float('inf') as default value is always larger, so val is picked
# if the key isn't present yet.
minimi[key] = min(val, minimi.get(key, inf))
my_unique_list = [[v, k] for k, v in minimi.items()]
通过使用字典作为中介,您可以在线性时间内过滤输入。
演示:
>>> my_list = [[4, 'C'], [1, 'A'], [3, 'B'], [2, 'A'], [5,'C']]
>>> minimi, inf = {}, float('inf')
>>> for val, key in my_list:
... minimi[key] = min(val, minimi.get(key, inf))
...
>>> minimi
{'C': 4, 'A': 1, 'B': 3}
>>> my_unique_list = [[v, k] for k, v in minimi.items()]
>>> my_unique_list
[[4, 'C'], [1, 'A'], [3, 'B']]
为什么要关心运行时间?因为随着您的输入增加,您的运行时间也会增加。对于需要 O(N^2)(二次)时间的方法,当您从 1000 个项目增加到 100 万个(因此是大小的 1000 倍)时,您的运行时间将增加 100 万倍!对于 O(N logN) 方法(使用排序的方法),运行时间将增加约 2000 倍,而上述线性方法将花费 1000 倍的时间,随着输入规模线性扩展。
对于大量输入,这可能会导致“需要一两个小时”与“需要数百万年”之间的差异。
这是该方法与 zamir 的排序和设置方法 (O(N logN)) 以及 TJC World 的 Pandas 方法(也是 O(N logN))之间的时间试验比较:
from string import ascii_uppercase
from functools import partial
from timeit import Timer
import random
import pandas as pd
def gen_data(N):
return [[random.randrange(1_000_000), random.choice(ascii_uppercase)] for _ in range(N)]
def with_dict(l, _min=min, _inf=float('inf')):
minimi = {}
m_get = minimi.get
for val, key in l:
minimi[key] = _min(val, m_get(key, _inf))
return [[v, k] for k, v in minimi.items()]
def with_set_and_sort(l):
already_encountered = set()
ae_add = already_encountered.add
return [i for i in sorted(l) if i[1] not in already_encountered and not ae_add(i[1])]
def with_pandas(l):
return (
pd.DataFrame(l)
.sort_values(by=0)
.drop_duplicates(1)
.to_numpy()
.tolist()
)
for n in (100, 1000, 10_000, 100_000, 1_000_000):
testdata = gen_data(n)
print(f"{n:,} entries:")
for test in (with_dict, with_set_and_sort, with_pandas):
count, total = Timer(partial(test, testdata)).autorange()
print(f"{test.__name__:>20}: {total/count * 1000:8.3f}ms")
print()
我已经使用了我所知道的所有性能小技巧;通过将它们缓存在循环之外的本地名称中来避免重复查找全局和属性。
这个输出:
100 entries:
with_dict: 0.028ms
with_set_and_sort: 0.032ms
with_pandas: 2.070ms
1,000 entries:
with_dict: 0.242ms
with_set_and_sort: 0.369ms
with_pandas: 2.312ms
10,000 entries:
with_dict: 2.331ms
with_set_and_sort: 5.639ms
with_pandas: 5.476ms
100,000 entries:
with_dict: 23.105ms
with_set_and_sort: 127.772ms
with_pandas: 40.330ms
1,000,000 entries:
with_dict: 245.982ms
with_set_and_sort: 2494.305ms
with_pandas: 578.952ms
因此,只有 100 个输入,排序方法可能看起来一样快(两种方式都有几毫秒的差异),但随着输入的增长,这种方法会加速失地。
Pandas 在这里各方面都失败了。数据框是一个很棒的工具,但这里的工具是错误的。它们是庞大的数据结构,因此对于小输入,它们的高开销使它们进入毫秒范围,远远落后于其他两个选项。在 10k 条目时,它开始优于排序和设置方法,但即使数据帧操作得到了高度优化,具有更大输入的排序运行时间的增长仍然无法击败线性方法。