有没有办法避免在字典中重复键值映射?

Is there a way to avoid repeating of key-value mappings in dictionary?

我有一本字典,可以将特定时间(从日期中获取)映射到特定数字。

time_of_day_mapping = {
    4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0,
    12: 1, 13: 1, 14: 1, 15: 1, 16: 1,
    17: 2, 18: 2, 19: 2, 20: 2, 21: 2, 22: 2, 23: 2,
    0: 3, 1: 3, 2: 3, 3: 3}

让我感到沮丧的是,有些值被映射到相同的值,例如 0、1、2 和 3 被映射到 3。我想以某种方式将字典键格式更改为范围或元组以避免重复这样的方式:

time_of_day_mapping = {
    range(4, 12): 0,
    range(12, 17): 1,
    range(17, 24): 2,
    range(0, 4): 3}

但是如果我只有一个值,那么我不确定如何使用范围作为键来有效地获取映射:

df['some_date'].apply(lambda x: time_of_day_mapping.get(x.hour)

我很乐意得到一些建议。或者从效率的角度来说,最好不要更改任何有关字典格式的内容?

您正在寻找双向映射,或者寻找键到集合的映射。

前者可以用bidict实现。

from bidict import bidict                                                                                                                                                                     

d = bidict([('a', 23), ('b', 42)])                                                                                                                                                            

d.inverse[23] # 'a'

后者可以通过defaultdict实现。

from collections import defaultdict                                                                                                                                                           

d = defaultdict(list)
d['a'].append(23)                                                                                                                                                                             
d['a'].append(42)                                                                                                                                                                             
d['a'] # [23, 42]    

有了理解列表,您可以执行以下操作:

from datetime import datetime
import pandas as pd

time_of_day_mapping = {range(4, 12): 0, range(12, 17): 1, range(17, 24): 2, range(0, 4): 3}

df = pd.DataFrame([datetime(2019,6,25,4,0,0), datetime(2019,6,25,12,0,0), datetime(2019,6,25,17,0,0), datetime(2019,6,25,2,0,0)], columns = ["some_date"])
df['my_new_col'] = df['some_date'].apply(lambda x: [time_of_day_mapping[key] for key in time_of_day_mapping if x.hour in key][0])

print(df)

输出

+----+----------------------+------------+
|    |      some_date       | my_new_col |
+----+----------------------+------------+
| 0  | 2019-06-25 04:00:00  |          0 |
| 1  | 2019-06-25 12:00:00  |          1 |
| 2  | 2019-06-25 17:00:00  |          2 |
| 3  | 2019-06-25 02:00:00  |          3 |
+----+----------------------+------------+

如果您最关心的是代码的可读性,您可以像以前那样定义字典,然后 "unpack" 将其恢复为原始形式(这样您就可以进行常规字典查找):

time_of_day_mapping = {
    range(4, 12): 0,
    range(12, 17): 1,
    range(17, 24): 2,
    range(0, 4): 3
}

time_of_day_mapping = dict((key, value) for range_obj, value in time_of_day_mapping.items() for key in list(range_obj))

print(time_of_day_mapping)

在 运行 这段代码之后,time_of_day_mapping 正是您问题中首次出现的字典。

让我建议,为了提高可读性,您使用 inclusive start-end 元组作为键,然后在理解中使用 range 它们,就像这样:

time_of_day_mapping = {
    (4, 11): 0,
    (12, 16): 1,
    (17, 23): 2,
    (0, 3): 3
}

time_of_day_mapping = dict((key, value) for (start, end), value in time_of_day_mapping.items() for key in list(range(start, end+1)))

print(time_of_day_mapping)

我认为在 d[12] != 0 时看到 (4,11):0 而不是 (4,12):0 更好。

您可以使用 tuple 而不是范围,只需在 dict

中搜索小时值

像这样:

from random import randint
import datetime

#use tuple instead of range
time_of_day_mapping = {
    (4, 5, 6, 7, 8, 9, 10, 11): 0,
    (12, 13, 14, 15, 16): 1,
    (17, 18, 19, 20, 21, 22, 23): 2,
    (0, 1, 2, 3): 3}

# here I created a list of random datetimes
dates = []
for i in range(0,10):
    date=datetime.datetime(randint(2005,2025), randint(1,12),randint(1,28),randint(1,23),randint(0,59))
    dates.append(date.hour)
print(dates)

res = [time_of_day_mapping[k] for d in dates for k in time_of_day_mapping if d in k]
print(res)