当与defaultdict结合使用时,计数排序会丢失。

4

我试图对由 (item_number, fruit) 元组构成的列表进行操作,计算每种水果出现的次数。使用 collections.Counter 就足够了。我还使用了 most_common()

但是,当我试图同时显示与特定类型的水果相对应的项目编号列表时,它们就会变得无序。

这是我的示例代码:

#!/usr/bin/env python

from collections import Counter, defaultdict

mylist = [
            (1, 'peach'),
            (2, 'apple'),
            (3, 'orange'),
            (4, 'apple'),
            (5, 'banana'),
            (6, 'apple'),
            (7, 'orange'),
            (8, 'peach'),
            (9, 'apple'),
            (10, 'orange'),
            (11, 'plum'),
            ]

# FIRST, HANDLE JUST COUNTING THE ITEMS

normal_list = []

# append to a simple list
for item_number, fruit in mylist:
    normal_list.append(fruit)

# prints just the name of each fruit and how many times it appears
for fruit, count in Counter(normal_list).most_common(10):
    print(f'{fruit}\tCount: {count}')  

# NOW TRY TO INCLUDE THE LIST IF ITEM NUMBERS ALSO

mydefaultdict = defaultdict(list)

# append to the defaultdict
for item_number, fruit in mylist:
    mydefaultdict[fruit].append(item_number)

# prints each fruit, followed by count, and finally the list of IPs for each
for fruit, item_list in Counter(mydefaultdict).most_common(10):
    print(f'{fruit}\tCount: {len(item_list)}\tList: {item_list}')

我得到了较为简单版本的预期输出:
apple   Count: 4
orange  Count: 3
peach   Count: 2
banana  Count: 1
plum    Count: 1

然而,当我尝试将item_number列表添加到其中时,结果不再排序,这在我使用小于水果品种总数的most_common()值时会造成混乱:
plum    Count: 1    List: [11]
banana  Count: 1    List: [5]
orange  Count: 3    List: [3, 7, 10]
apple   Count: 4    List: [2, 4, 6, 9]
peach   Count: 2    List: [1, 8]

我相信我在这里可以做些不同的事情,但我不太确定应该做什么。

2个回答

3
Counter(mydefaultdict)并不是你所想象的那样。你正在将一个列表defaultdict提供给Counter,其目的是统计出现次数,而不是计算列表长度。实际上,你的Counter对象的只是列表,而不是整数。Counter不会报错,因为它是dict的子类,并且可以使用另一个字典进行初始化。
为了按最长列表排序,你可以使用具有自定义函数的heapq.nlargest
from heapq import nlargest

for fruit, item_list in nlargest(10, mydefaultdict.items(), key=lambda x: len(x[1])):
    print(f'{fruit}\tCount: {len(item_list)}\tList: {item_list}')

apple   Count: 4    List: [2, 4, 6, 9]
orange  Count: 3    List: [3, 7, 10]
peach   Count: 2    List: [1, 8]
banana  Count: 1    List: [5]
plum    Count: 1    List: [11]

当涉及到你所提出的问题时,需要注意的主要点是每个键的“计数”等于与该键相关联的列表的长度。 items 提取键值组合,而 len(x [1]) 获取值(即列表)的长度。对于您更广泛的问题,建议您单独制定一个特定的问题。 - jpp
@ChrisJones,我很困惑。你的问题似乎围绕着“结果不再排序”。你要按什么排序?看起来这是你想要的结果,但如果不是,请明确一下。 - jpp
当你使用most_common()时,它会按最常见到最不常见的顺序进行排序。但是,当我添加了查看使其最常见的实际项目的功能时,它们不再按最常见到最不常见的顺序出现。或者,如果我们再次走出这个测试用例,我发现我的300台路由器中有73台具有完全相同的配置。但是脚本没有告诉我这73台路由器中任何一台的名称,以便我实际上可以SSH到其中一台。那时,我决定将路由器列表添加到每行中。这有意义吗?我试图通过一个测试用例来简化代码以使其更清晰。 - Chris Jones
它们不再按照最常见到最不常见的顺序出现。这是因为在使用most_common之前,您正在定义不同的 Counter对象。第一个是Counter(normal_list),第二个是Counter(mydefaultdict)。没有理由这些应该在应用most_common时给出相同的结果。 - jpp
@ChrisJones,抱歉再重复一遍,但这正是我们正在帮助的地方。例如, nlargest(10, mydefaultdict.items(), key=lambda x: len(x[1])) 是不同的输入。 - jpp
显示剩余8条评论

3

这部分内容比较困难:

Counter(mydefaultdict)

你的对象mydefaultdict已经填充了列表作为值,但是Counter对象通常具有正整数作为值。这实际上不是一个错误,因为Counter是字典子类,因此它将接受任何字典作为初始化器参数。但是存在一个问题:most_common不再返回合理的结果(如果你感到奇怪,它实际上是基于列表按照词典顺序进行排列)。
也许更清晰的做法是这样的:
most_common_fruits = sorted(mydefaultdict, key=lambda f: len(mydefaultdict[f]), reverse=True)
for fruit in most_common_fruits:
    item_list = mydefaultdict[fruit]
    ...

现在的输出结果如下:

apple   Count: 4    List: [2, 4, 6, 9]
orange  Count: 3    List: [3, 7, 10]
peach   Count: 2    List: [1, 8]
banana  Count: 1    List: [5]
plum    Count: 1    List: [11]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接