添加键的值并按在 Python 中的字典列表中出现的键对其进行排序

Posted

技术标签:

【中文标题】添加键的值并按在 Python 中的字典列表中出现的键对其进行排序【英文标题】:Add values of keys and sort it by occurrence of the keys in a list of dictionaries in Python 【发布时间】:2017-12-25 03:04:52 【问题描述】:

我真的是 Python 新手,我遇到了以下我需要解决的问题。 我有一个来自 Apache Log 的日志文件,如下所示:

[01/Aug/1995:00:54:59 -0400] "GET /images/opf-logo.gif HTTP/1.0" 200 32511
[01/Aug/1995:00:55:04 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635
[01/Aug/1995:00:55:06 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 403 298
[01/Aug/1995:00:55:09 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635
[01/Aug/1995:00:55:18 -0400] "GET /images/opf-logo.gif HTTP/1.0" 200 32511
[01/Aug/1995:00:56:52 -0400] "GET /images/ksclogosmall.gif HTTP/1.0" 200 3635

我必须返回请求最多的 10 个对象及其传输的累积字节数。我只需要包含带有成功(HTTP 2xx)响应的 GET 请求。

所以上面的日志会变成:

/images/ksclogosmall.gif 10905
/images/opf-logo.gif 65022

到目前为止,我有以下代码:

import re
from collections import Counter, defaultdict
from operator import itemgetter
import itertools
import sys

log_file = "web.log"
pattern = re.compile(
      r'\[(?P<date>[^\[\]:]+):(?P<time>\d+:\d+:\d+) (?P<timezone>[\-+]?\d\d\d\d)\] '
      + r'"(?P<method>\w+) (?P<path>[\S]+) (?P<protocol>[^"]+)" (?P<status>\d+) (?P<bytes_xfd>-|\d+)')

dict_list = []

with open(log_file, "r") as f:
    for line in f.readlines():
        if re.search("GET", line) and re.search(r'HTTP/[\d.]+"\s[2]\d2', line):
            try:
                log_line_data = pattern.match(line)
                path = log_line_data["path"]
                bytes_transferred = int(log_line_data["bytes_xfd"])
                dict_list.append(path: bytes_transferred)
            except:
                print("Unexpected Error: ", sys.exc_info()[0])
                raise
    f.close()

print(dict_list)

此代码打印以下字典列表。

['/images/opf-logo.gif': 32511, 
'/images/ksclogosmall.gif': 3635, 
'/images/ksclogosmall.gif': 3635, 
'/images/opf-logo.gif': 32511, 
'/images/ksclogosmall.gif': 3635]

我不知道如何从这里得到结果:

/images/ksclogosmall.gif 10905
/images/opf-logo.gif 65022

这个结果基本上是与相似键相对应的值相加,按特定键以降序顺序出现的次数排序。

注意:我尝试使用 colllections.Counter 无济于事,这里我想按 key 出现的次数排序。

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

您可以使用 collections.Counter 和 update 它来为每个对象添加传输的字节数:

from collections import Counter
c = Counter()
for d in dict_list:
    c.update(d)
occurrences=Counter([list(x.keys())[0] for x in dict_list])
sorted(c.items(), key=lambda x: occurrences[x[0]], reverse=True)

输出:

[('/images/ksclogosmall.gif', 10905), ('/images/opf-logo.gif', 65022)]

【讨论】:

我试过了,但这不是我需要的。我需要按特定键出现的次数对字典列表进行排序,同时添加它们的值,最终输出将从最高到最低,例如:/images/ksclogosmall.gif 10905 /images/opf-logo。 gif 65022 还是一样,都是按值排序的。我希望它按特定键发生的次数排序,因此即使与 '/images/opf-logo.gif' 键对应的值是 65022 与 '/images/ksclogosmall.gif' 的值是 10905 相比,它应该仍然在顶部,因为关键字“/images/ksclogosmall.gif”在字典列表中出现了 3 次,而另一个只出现了两次。很抱歉有任何混淆,我真的在这部分卡了一段时间了。 好的,这次真的修好了! 使用operator.add 减少一堆计数器效率低下,因为它需要一次又一次地复制计数器并扫描负条目。使用 dict_list 的每个元素构造一个初始 Counter 和 update 它会更快。【参考方案2】:

首先,字典列表对于这种类型的数据并没有实际意义。由于每个字典只有一个键值对,因此只需构建一个元组列表(或者如果您想要更多可读性,则构建一个 namedtuples 列表)。

tuple_list.append((path, bytes_transferred))

现在,获得您想要的结果将更加直接。我个人会使用defaultdict

from collections import defaultdict

tracker = defaultdict(list)
for path, bytes_transferred in tuple_list:
    tracker[path].append(bytes_transferred)
# '/images/ksclogosmall.gif': [3635, 3635, 3635], '/images/opf-logo.gif': [32511, 32511]

print([(p, sum(b)) for p, b in sorted(tracker.items(), key=lambda i: -len(i[1]))])
# [('/images/ksclogosmall.gif', 10905), ('/images/opf-logo.gif', 65022)]

【讨论】:

【参考方案3】:

你可以循环你的字典并将值存储在一个新的字典中:

results = 
for d in dict_list:
    for k, v in d.items():
        total = results.get(k, 0) # get previously stored value, 0 if none
        results[k] = total + v

【讨论】:

【参考方案4】:

这可能不是最优雅的解决方案,但它会起作用:

freq = 
with open('test.txt') as f:
    lines = f.read().splitlines()

    for line in lines:
        if 'GET' in line and 'HTTP' in line and '200' in line:
            path = line.split()[3]
            occur = int(line.split()[-1])
            freq[path] = freq.get(path, 0) + occur

frequency = k: v for k, v in sorted(freq.items(), key=lambda x: x[1])

所以对于您提供的日志 sn-p:

print(frequency)
>>> '/images/ksclogosmall.gif': 10905, '/images/opf-logo.gif': 65022

【讨论】:

我实际上有这个输出,我需要的是按此字典列表中键出现的次数及其累积值排序的结果。 /images/ksclogosmall.gif 10905 /images/opf-logo.gif 65022 @leo_21 啊抱歉我误会了,更新了我的答案。 不用担心。我认为它仍然给出了按值排序的答案,不是吗?抱歉,我不确定我的代码是否正确。如何按键以 desc 顺序出现的次数进行排序,并添加它们的累积值? 我明白了 - 我会更新我的答案,但 @Imran 是一种更好的方法,我相信你在寻找什么。 是的,谢谢您的意见。【参考方案5】:

另一种选择,两行

....
path = log_line_data["path"]
if [x for x in range(len(dict_list)) if path in dict_list[x].keys()]:
    continue

输出

['/images/opf-logo.gif': 32511, '/images/ksclogosmall.gif': 3635]

【讨论】:

【参考方案6】:

如果你想折叠

['/images/opf-logo.gif': 32511, 
'/images/ksclogosmall.gif': 3635, 
'/images/ksclogosmall.gif': 3635, 
'/images/opf-logo.gif': 32511, 
'/images/ksclogosmall.gif': 3635]

放入字典中,将具有相同键的值相加:

    创建一个新的空字典 遍历每个字典,检查新字典中是否存在键 如果key(文件路径)不存在,复制过来 如果存在,添加值

```

total = 

for d in all:
    for k, v in d.items():
             if k in total:
                     total[k] += v
             else:
                     total[k] = v

print(total)
'/images/opf-logo.gif': 65022, '/images/ksclogosmall.gif': 10905

【讨论】:

以上是关于添加键的值并按在 Python 中的字典列表中出现的键对其进行排序的主要内容,如果未能解决你的问题,请参考以下文章

python 一个由字典构成的列表,修改其中1个字典的键的值,却把该列表所有字典相同的键的值都一起修改了?

更改列表中每个字典的特定键的值 - python

Python添加2个带有常用键的列表到字典

Python 字典方法

Python 字典方法

python中怎么改变一个字典的对应键的值