我有一个非常大的文件,我正在解析并从该行获取键值。我只想要第一个键和值,只有一个值。也就是说,我正在删除重复的值
所以它看起来像:
{
A:1
B:2
C:3
D:2
E:2
F:3
G:1
}
它会输出:
{E:2,F:3,G:1}
这有点令人困惑,因为我并不在乎关键是什么。所以上面的E可以用B或D代替,F可以用C代替,G可以用A代替。
这是我发现的最佳方法,但随着文件变大,速度非常慢。
mapp = {}
value_holder = []
for i in mydict:
if mydict[i] not in value_holder:
mapp[i] = mydict[i]
value_holder.append(mydict[i])
每次都必须通过value_holder查看:(有更快的方法吗?
答案 0 :(得分:6)
是的,一个微不足道的变化会让它变得更快:
value_holder = set()
(嗯,您还必须将append
更改为add
。但仍然非常简单。)
使用集合代替列表意味着每个查找都是O(1)而不是O(N),因此整个操作是O(N)而不是O(N ^ 2)。换句话说,如果你有10,000行,你就会进行10,000次哈希查找,而不是50,000,000次比较。
这个解决方案的一个警告 - 以及所有其他发布的 - 是它要求值可以清除。如果它们不可清洗,但它们具有可比性,您仍然可以通过使用排序集(例如,来自blist
库)获得O(NlogN)而不是O(N ^ 2)。如果它们既不可清洗也不可排序......好吧,你可能想找到一些方法来生成可用的(或可排序的)用作“第一次检查”的东西,然后只用于实际匹配的“第一次检查”匹配,它将到达O(NM),其中M是平均哈希冲突数。
您可能希望了解标准库文档中itertools
recipes中unique_everseen
的实现方式。
请注意,字典实际上没有订单,所以没有办法选择“第一”副本;你会随意得到一个。在这种情况下,还有另一种方法:
inverted = {v:k for k, v in d.iteritems()}
reverted = {v:k for k, v in inverted.iteritems()}
(这实际上是decorate-process-undecorate成语的一种形式,没有任何处理。)
但是,不是构建dict
然后对其进行过滤,而是通过在阅读时进行过滤,使事情变得更好(更简单,更快,更节省内存,并保持顺序)。基本上,随着时间的推移,set
与dict
保持一致。例如,而不是:
mydict = {}
for line in f:
k, v = line.split(None, 1)
mydict[k] = v
mapp = {}
value_holder = set()
for i in mydict:
if mydict[i] not in value_holder:
mapp[i] = mydict[i]
value_holder.add(mydict[i])
这样做:
mapp = {}
value_holder = set()
for line in f:
k, v = line.split(None, 1)
if v not in value_holder:
mapp[k] = v
value_holder.add(v)
事实上,您可能需要考虑编写一个包含它的one_to_one_dict
(或者搜索PyPI模块和ActiveState配方以查看是否有人已经为您编写了它),那么您只需编写:< / p>
mapp = one_to_one_dict()
for line in f:
k, v = line.split(None, 1)
mapp[k] = v
答案 1 :(得分:2)
我并不完全清楚你正在做什么,但set
是删除重复项的好方法。例如:
>>> k = [1,3,4,4,5,4,3,2,2,3,3,4,5]
>>> set(k)
set([1, 2, 3, 4, 5])
>>> list(set(k))
[1, 2, 3, 4, 5]
虽然它取决于您正在加载的输入结构,但可能有一种方法可以简单地使用set
,这样您就不必每次都要遍历整个对象。如果有任何匹配的密钥 - 而是通过set
运行一次。
答案 2 :(得分:2)
正如其他人所提到的,加快这种速度的第一种方法是使用set
来记录看到的值,因为检查集合上的成员资格要快得多。
我们还可以使用dict comprehension:
缩短范围seen = set()
new_mapp = {k: v for k, v in mapp.items() if v not in seen or seen.add(i)}
if case需要一些解释:我们只添加我们之前没有看过该值的键/值对,但是我们稍微使用or
来确保将任何看不见的值添加到集合中。当set.add()
返回None
时,它不会影响结果。
与往常一样,在2.x中,用户dict.iteritems()
超过dict.items()
。
答案 3 :(得分:0)
使用set
代替list
会大大加快你的速度......
答案 4 :(得分:-1)
您说您正在读取一个非常大的文件,并且只想保留第一次出现的密钥。我原先假设这意味着你关心密钥/值对在非常大的文件中出现的顺序。这段代码会做到这一点并且速度很快。
values_seen = set()
mapp = {}
with open("large_file.txt") as f:
for line in f:
key, value = line.split()
if value not in values_seen:
values_seen.add(value)
mapp[key] = value
您使用list
来跟踪代码所看到的密钥。搜索list
非常慢:列表越大,速度越慢。 set
要快得多,因为查找接近于恒定时间(不要慢得多,或者可能慢一点,列表越大)。 (dict
也与set
的工作方式相同。)
答案 5 :(得分:-1)
你的部分问题是,当迭代通过时,dicts不会保留任何类型的逻辑顺序。他们使用哈希表来索引项目(参见this great article)。因此,在这种数据结构中没有“第一次出现价值”的真实概念。执行此操作的正确方法可能是键值对列表。例如:
kv_pairs = [(k1,v1),(k2,v2),...]
或者,因为文件太大,最好使用python提供的优秀文件迭代来检索k / v对:
def kv_iter(f):
# f being the file descriptor
for line in f:
yield ... # (whatever logic you use to get k, v values from a line)
Value_holder是set变量的理想选择。你真的只是在测试value_holder。由于值是唯一的,因此可以使用类似的散列方法更有效地索引它们。所以它最终会有点像这样:
mapp = {}
value_holder = set()
for k,v in kv_iter(f):
if v in value_holder:
mapp[k] = v
value_holder.add(v)