【问题标题】:Gen Fill Rates on Varying Nested Dictionaries in Python?Python中不同嵌套字典的Gen填充率?
【发布时间】:2018-01-25 22:09:20
【问题描述】:

我有一个在 Python 中接收嵌套字典的进程:

示例嵌套字典架构(伪代码)

key1: value1,
key2: dict(
  key3: value2,
  key4: value3,
),
key5: list(value4,value5) # any value is fine, just not empty or null

示例嵌套字典数据(伪代码)

key1: 'value',
key2: dict(
  key3: '',
  key4: 12345,
),
key5: list()

我想遍历/扫描这个字典并检查每个键是否有一个值(不是 Null 或空白 - false/0 是可以的)。我将需要扫描一堆相同的字典以获得该组字典的整体“填充率”。该流程每次运行时都会看到不同格式的dicts集,因此它需要自动生成填充率报告:

上面单个嵌套示例的示例填充率(理想情况下是平面字典):

key1: 1
key2: 1
key2-key3: 0
key2-key4: 1
key5: 0

如果我们扫描十个相同结构的字典,例如,我们可能会看到这样的 :fill rate":

key1: 5
key2: 6
key2-key3: 6
key2-key4: 4
key5: 3

问题

  1. 扫描不同结构的字典以生成填充率的最 Pythonic 方法是什么?如果我必须这样做数百万次,有没有更有效的方法?

  2. 创建平面 dict 来存储计数的最 Pythonic 方式是什么?如何更新它?

【问题讨论】:

  • 澄清一下:“填充率”是指您希望在最终看到的所有字典中查看多少次,例如指定了key1?你的例子不清楚。
  • 我更新了这个问题,希望能更清楚。 Gen 的意思是:检查每个字段,如果它已设置并且不是 Null 或空白,则将其计为“已填充”。通过填充率,我的意思是在一组具有相同结构的字典中,每个键值对不为空或为空的频率。

标签: python loops counting


【解决方案1】:

这是我的看法:

扫描不同结构的字典以生成填充率的最 Pythonic 方法是什么?

递归。特别是,我将遍历子树的结果返回给调用者。调用者负责将多个子树合并到它自己的树的结果中。

如果我必须这样做数百万次,有没有更有效的方法?

大概吧。尝试一种解决方案,看看它是否 A) 正确和 B) 是否足够快。如果两者兼而有之,请不要费心寻找最有效的方法。

创建一个平面字典来存储计数的最 Pythonic 方式是什么?如何更新它?

通过使用 Python 附带的库之一。在这种情况下,collections.Counter()。并通过调用其.update() 函数。

from collections import Counter
from pprint import pprint

example1_dict = {
    'key1': 'value',
    'key2': {
        'key3': '',
        'key4': 12345,
    },
    'key5': list()
}

example2_dict = {
    'key1': 'value',
    'key7': {
        'key3': '',
        'key4': 12345,
    },
    'key5': [1]
}

def get_fill_rate(d, path=()):
    result = Counter()
    for k, v in d.items():
        if isinstance(v, dict):
            result[path+(k,)] += 1
            result.update(get_fill_rate(v, path+(k,)))
        elif v in (False, 0):
            result[path+(k,)] += 1
        elif v:
            result[path+(k,)] += 1
        else:
            result[path+(k,)] += 0
    return result

def get_fill_rates(l):
    result = Counter()
    for d in l:
        result.update(get_fill_rate(d))
    return dict(result)

result = get_fill_rates([example1_dict, example2_dict])

# Raw result
pprint(result)

# Formatted result
print('\n'.join(
    '-'.join(single_key for single_key in key) + ': ' + str(value)
    for key, value in sorted(result.items())))

结果:

{('key1',): 2,
 ('key2',): 1,
 ('key2', 'key3'): 0,
 ('key2', 'key4'): 1,
 ('key5',): 1,
 ('key7',): 1,
 ('key7', 'key3'): 0,
 ('key7', 'key4'): 1}
key1: 2
key2: 1
key2-key3: 0
key2-key4: 1
key5: 1
key7: 1
key7-key3: 0
key7-key4: 1

【讨论】:

    【解决方案2】:

    好的,我想我解决了。我做了一些非常小的测试,但我认为这可行:

    def scan_dict(d):
        counts = {}
        for k, v in d.items():
            if isinstance(v, dict):
                subcounts = scan_dict(v)
                for subkey, subcount in subcounts.items():
                    new_key = str(k) + "-" + str(subkey)
                    count = counts.get(new_key, 0)
                    counts[new_key] = count + subcount
            key = str(k)
            count = counts.get(key, 0)
            counts[key] = count + 1
        return counts
    
    def scan_all_dicts(ds):
        total_counts = {}
        for d in ds:
            counts = scan_dict(d)
            for k, v in counts.items():
                count = total_counts.get(k, 0)
                total_counts[k] = count + v
        return total_counts
    

    本质上,有一个递归函数可以扫描每个字典并计算其中的所有内容以及它找到的任何子字典。

    “驱动程序”是第二个函数,它接受一个可迭代(例如列表)的 dicts 并通过第一个函数运行它们,然后返回所有值的扁平列表。

    我没有检查这些值以确保它们“不是空白”;我将把它留给你。

    【讨论】:

      【解决方案3】:

      你可以试试这样的:

      example1_dict = {
          'key1': 'value',
          'key2': {
              'key3': '',
              'key4': 12345,
          },
          'key5': list()
      }
      
      
      
      example={}
      for ka,l in example1_dict.items():
          if isinstance(l,dict):
              def hi(fg, track=''):
                  print(fg)
                  for i, k in fg.items():
                      track="{}-{}".format(ka,i)
                      if i not in example:
                          example[track] = 1
                      else:
                          example[track] += 1
                      if isinstance(k, dict):
                          return hi(k)
      
      
      
              print(hi(l))
          elif l:
              example[ka]=1
          else:
              example[ka]=0
      print(example)
      

      输出:

      {'key5': 0, 'key2-key4': 1, 'key1': 1, 'key2-key3': 1}
      

      【讨论】:

        【解决方案4】:

        递归是解决这个问题的最 Pythonic 的方法;但是,此解决方案使用装饰器来更新全局字典以存储整体填充率。使用collections.defaultdictfinal_dict 可以通过get_occurences 的每个换行多次更新:

        from collections import defaultdict
        import re
        final_dict = defaultdict(int)
        def fill(f):
           def update_count(structure, last):
             data = f(structure, last=None)
             def update_final(d):
                for a, b in d.items():
                    global final_dict
                    final_dict[a] += int(bool(b)) if not isinstance(b, dict) else int(bool(update_final(b)))
              update_final(data)
           return update_count
        
        @fill
        def get_occurences(d, last=None):
           return {"{}-{}".format(last, a) if last else a:int(bool(b)) if not isinstance(b, dict) else get_occurences(b, a) for a, b in d.items()}
        
        structures = [{'key1':'value', 'key2':{'key3':'', 'key4':12345}, 'key5':[]}, {'key1':18, 'key2':'value1', 'key3':['James', 'Bob', 'Bill']},{'key1':'value2', 'key2':{'key3':'233', 'key4':12345}, 'key5':100}]
        for structure in structures:
           get_occurences(structure)
        
        for i in sorted(final_dict.items(), key=lambda (c, d):(int(re.findall('\d+$', c)[0]), bool(re.findall('\w+-\w+', c)))):
          print("{}: {}".format(*i))
        

        输出:

        {'key2-key3': 1, 'key2-key4': 2, 'key1': 3, 'key2': 1, 'key5': 1, 'key3': 1}
        

        输出:

        key1: 3
        key2: 1
        key3: 1
        key2-key3: 1
        key2-key4: 2
        key5: 1
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-11-25
          • 2018-01-20
          • 2017-08-31
          • 1970-01-01
          • 2020-06-08
          • 1970-01-01
          • 2020-12-06
          • 1970-01-01
          相关资源
          最近更新 更多