【问题标题】:Is there is 'dict.setdefault' equivalent for sets?集合是否有等效的“dict.setdefault”?
【发布时间】:2013-10-27 19:47:05
【问题描述】:

使用集合时的常见模式如下:

number_list = [1,5,7,2,4,4,1,3,8,5]
number_set = set()

for number in number_list:

   #we only want to process the number if we haven't already processed it
   if(number not in number_set):
       number_set.add(number)

       #do processing of 'number' here now that we know it's not a duplicate

if(number not in number_set):number_set.add(number) 行让我很烦,因为我们在这里进行了两次哈希查找,而实际上我们应该只需要一次。

字典有“setdefault”操作,解决了一个非常相似的问题:“如果字典中存在key,则返回值,否则插入这个默认值,然后返回默认值”。如果您天真地执行此操作,IE 以下,您执行两次哈希查找,但 setdefault 允许您一次完成

if item_key in dict:
   dict[item_key].append(item_value)
else:
   dict[item_key] = [item_value]

集合是否有等效的操作? if(number_set.check_if_contains_and_then_add(number)): 之类的东西,但名称要好得多。

【问题讨论】:

  • 你为什么不能只做number_set = set(number_list)
  • @AshishNitinPatil 在我给出的示例中这将是正确的选择,但如果您在开始之前不知道列表的全部内容,或者如果您有一个 iderator你不想一下子吃完
  • 既然某样东西不能多次出现在一个集合中,那就无条件地添加它,如果它已经在那里,什么都不会改变,也不会造成任何伤害。
  • 如果 set.add 返回一个布尔值来指示实际上是否有任何新添加的东西,那就太好了,但事实并非如此。

标签: python dictionary


【解决方案1】:

如果分析器告诉您哈希查找对运行时间的贡献很大,那么这可能会解决它。

def add_value(container, value):
    oldlen = len(container)
    container.add(value)
    return len(container) != oldlen

if add_value(number_set, number):
    # process number

但是为什么会这样呢?也许是由于__hash__ 方法较慢,尽管我现在可以告诉您(a)散列整数并不慢,并且(b)如果可能的话,最好使用慢速 __hash__ 缓存结果的类而不是减少调用次数。或者可能是由于速度较慢的__eq__,这更难处理。最后,如果内部查找机制本身很慢,那么您可能无法加快程序速度,因为运行时一直在进行哈希查找,在范围内查找名称。

set.add 返回一个指示集合是否更改的值可能会很好,但我认为这个想法违反了 Python 库的原则(诚然不是普遍支持的),即变异操作不会返回一个值,除非它是操作的基础。所以pop() 函数当然会返回一个值,但list.sort() 会返回None,即使它偶尔会在返回self 时对用户有用。

我想你可以这样做:

def deduped(iterable):
    seen = set()
    count = 0
    for value in iterable:
        seen.add(value)
        if count != len(seen):
            count += 1
            yield value

for number in deduped(number_list):
    # process number

当然,重复的哈希查找是否存在任何问题纯属猜测:我通常会像在原始代码中那样使用if not in 测试编写这些函数中的任何一个,并且该函数的目的是简化调用代码,而不是避免多余的哈希查找。

【讨论】:

    【解决方案2】:

    没有。

    setdefault 方法用于设置字典中键的默认,集合没有值,因此完全没有意义。

    如果顺序无关紧要,试试这个。

    number_list = [1,5,7,2,4,4,1,3,8,5]
    number_set = set(number_list)
    
    for number in number_set:
       #do processing of 'number' here now that we know it's not a duplicate
    

    【讨论】:

    • 很好的答案,但你的意思是失去对 add() 方法的毫无意义的调用。
    • @Duncan 是的,一开始我没有注意到。感谢您的提醒。
    • 这个答案在事先知道整个列表的情况下有效,但如果您使用的迭代器不想一次全部使用,或者像广度优先搜索这样的东西整个列表在算法开始时是未知的
    • @Elliott 不管sn-p 的缺点如何,答案仍然是“否”,没有setdefault 等效的集合。另外,e in setset.add(e) 实际上并不是同一个操作。
    • @OdraEncoded 也许我应该更清楚一点,但关键是我们毫无意义地进行了多次哈希查找,一个用于“in”操作,另一个用于“add”操作,而不是那个“in”和“add”做同样的事情
    【解决方案3】:

    你为什么不直接做number_set.add(number)? setdefault 的要点是它不会覆盖键的现有值(如果存在)。但是一个集合没有值,只有一个键,所以覆盖是无关紧要的。

    【讨论】:

    • “你为什么不直接做number_set.add(number)?” -- 因为没有if not in 测试,number_list 中的任何重复项都将被处理两次。
    【解决方案4】:

    不,sets 没有 setdefault 类型方法,但你可以这样做:

    number_list = [1,5,7,2,4,4,1,3,8,5]
    number_set = set()
    
    for number in number_list:
       if number not in number_set and not number_set.add(number):
           #do somethihng here
    

    仅当number not in number_setTrue 时才会调用not number_set.add(number) 条件。

    使用它,您可以以有序的方式处理独特的项目(保持顺序)。

    >>> number_list = [1,5,7,2,4,4,1,3,8,5]
    >>> seen = set()
    >>> [x for x in number_list if x not in seen and not seen.add(x)]
    [1, 5, 7, 2, 4, 3, 8]
    

    如果订单无关紧要,那么只需在number_list 上致电set()

    >>> set(number_list)
    {1, 2, 3, 4, 5, 7, 8}
    

    【讨论】:

    • 我喜欢这在一行中完成这两项操作,但我觉得and not number_set.add(number) 将无法读取 - 确实“void”方法实际上返回 None,但很难将其与依赖一个实际的布尔返回值
    • 我会写 number_set.add(number) is None 而不是 not number_set.add(number)。对我来说似乎更清楚了。
    • 将该函数放在 if 子句条件中绝对没有意义。
    猜你喜欢
    • 1970-01-01
    • 2011-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多