从Python dict获得独特的第一次出现的更有效方法

时间:2012-12-27 22:56:59

标签: python maps unique dictionary

我有一个非常大的文件,我正在解析并从该行获取键值。我只想要第一个键和值,只有一个值。也就是说,我正在删除重复的值

所以它看起来像:

{
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查看:(有更快的方法吗?

6 个答案:

答案 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 recipesunique_everseen的实现方式。

请注意,字典实际上没有订单,所以没有办法选择“第一”副本;你会随意得到一个。在这种情况下,还有另一种方法:

inverted = {v:k for k, v in d.iteritems()}
reverted = {v:k for k, v in inverted.iteritems()}

(这实际上是decorate-process-undecorate成语的一种形式,没有任何处理。)

但是,不是构建dict然后对其进行过滤,而是通过在阅读时进行过滤,使事情变得更好(更简单,更快,更节省内存,并保持顺序)。基本上,随着时间的推移,setdict保持一致。例如,而不是:

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)