【问题标题】:Python find object in a listPython在列表中查找对象
【发布时间】:2011-07-04 11:20:02
【问题描述】:

我有一份人员名单:

[
    {'name' : 'John', 'wins' : 10 },
    {'name' : 'Sally', 'wins' : 0 },
    {'name' : 'Fred', 'wins' : 3 },
    {'name' : 'Mary', 'wins' : 6 }
]

我正在使用名称列表 (['Fred', 'Mary', 'Sally']) 添加胜利。我不知道这个名字是否已经在人员列表中,如果没有,我需要插入一条新记录。目前我正在做以下事情:

name = 'John'
person = None
pidx = None
for p in people_list:
    if p['name'] == name:
        person = p
        pidx = people_list.index(p)
        break
if person is None:
    person = {'name' : name, 'wins' : 0}
person['wins'] += 1
if pidx is None:
    people_list.append(person)
else
    people_list[pidx] = person

有没有更好的方法来使用列表来做到这一点?鉴于我将其保存到 MongoDB,我不能使用 dict,因为它将保存为对象,并且我想使用本机数组函数进行排序和映射,这些函数不适用于对象。

【问题讨论】:

    标签: python list search indexing


    【解决方案1】:

    是的,使用字典。

    wins = {}
    for name in winners:
        wins.setdefault(name, 0)
        wins[name] += 1
    

    编辑:

    index = {}
    for name in wins:
        person = index.setdefault(name, { 'name' : name, 'wins': 0 })
        if person['wins'] == 0:
            person_list.append(person)
        person['wins'] += 1
    

    【讨论】:

    • 我不想使用dict。我想使用list。说 "use a dict" 是没用的,因为我希望它是一个列表。我将其保存到 MongoDB,使用 dict 使其成为对象,并且我想使用一些本机数组函数。
    • @Josh K:请参阅我关于使用 listset 的回答
    • @MAK:有什么不清楚的地方?我知道使用dict 会提供更简洁的访问模式,但我不能。
    • @Josh:答案仍然是“使用字典”。请参阅我的编辑,了解如何做到这两点。
    【解决方案2】:

    您的访问模式要求使用不同的数据结构(或至少另一种辅助数据结构)。如果您正在使用列表,则在您正在执行的操作时扫描列表实际上是正确的做法,但您不应该使用列表(如果您希望它高效,无论如何)。

    如果列表的顺序无关紧要,您应该使用字典(python dict)。如果是这样,您应该使用来自collections 模块的OrderedDict

    您还可以使用两个单独的数据结构 - 您已经拥有的列表,另外还有一个 set 仅包含列表中的名称,这样您就可以快速访问测试是否包含。但是,set 并不能帮助您快速访问实际名称数据(您仍然需要在列表中进行线性搜索),因此如果您只是测试包含,它只会是一个有用的模式,但否则总是在插入列表时遍历列表。

    编辑:您可能真正想要的是一个列表和一个字典,其中字典是name 和列表中的索引之间的映射。或者,您仍然可以使用dictOrderedDict,但在插入时使用dict.iteritems() 将它们作为数组插入到Mongo 中以创建数组(或看起来像Mongo 的数组)。您可以使用从 zipitertools 中的各种变量来动态构建结果数组中所需的对象。

    【讨论】:

    • 请注意 collections.OrderedDict 在 Python 2.7 中是新的,但对于早期版本,OrderedDict 有一个等效的配方。详情请参阅documentation
    【解决方案3】:

    我在这里假设您不想使用列表以外的任何结构。您的代码应该可以工作,尽管您在更新字典后不必要地将字典写回列表。字典是通过引用复制的,所以一旦你更新它,它就会在列表中保持更新。稍作整理后,您的代码可能如下所示:

    def add_win(people_list, name):
        person = find_person(people_list, name)
        person['wins'] += 1
    
    def find_person(people_list, name):
        for person in people_list:
            if person['name'] == name:
                return person
        person = {'name': name, 'wins': 0}
        people_list.append(person)
        return person
    

    【讨论】:

      【解决方案4】:

      如果您不希望 dict 永久使用,请暂时使用。

      people = [
          {'name' : 'John', 'wins' : 10 },
          {'name' : 'Sally', 'wins' : 0 },
          {'name' : 'Fred', 'wins' : 3 },
          {'name' : 'Mary', 'wins' : 6 }
      ]
      
      wins = ['Fred', 'Mary', 'Sally']
      
      people_dict = dict((p["name"], p) for p in people)
      
      for winner in wins:
          people_dict[winner].setdefault("wins", 0)
          people_dict[winner]["wins"] += 1
      
      people = people_dict.values()
      

      【讨论】:

      • 你能把list转换成dict吗?我正在从 MongoDB 中提取一个列表。
      • 这正是在定义 people_dict 的行的示例代码中发生的事情。键是名称,值是您拥有的字典。
      • 与列表/字典相互转换的处理开销是多少?
      • 小于每次要在列表中增加一个数字时搜索列表的处理开销。
      【解决方案5】:

      这种特殊情况是由collections.Counter 类型实现的。除了数组生成器,这是一个表达式:

      [{'name':name, 'wins':wins}
       for name, wins in Counter(names).items()]
      

      如果你想要一个特定的顺序,sorted() 是最简单的方法(这也使用普通的生成器(),而不是数组生成器[],因为它是临时的):

      sorted(({'name':name, 'wins':wins} for name, wins in Counter(names).items()),
             key=lambda item: item['name'])
      

      其中item['name'] 可以是item['wins'] 或任何其他类似的表达式。

      【讨论】: