【问题标题】:Python - find recurring pairs/groups of values in dictionaryPython - 在字典中查找重复的值对/组
【发布时间】:2015-05-21 12:00:04
【问题描述】:

我有以下脚本,它循环遍历 css 规则的文本文件,并将每个规则及其属性存储在字典中(欢迎改进代码,我才刚刚开始使用 Python):

findGroups.py

import sys
source = sys.argv[1]
temp = open('pythonTestFile.txt', 'w+')
di = {}
with open(source, 'r') as infile:
    for line in infile:
        # if line starts with . or #, contains _ or - between 
        # words and has a space and opening brace(ie is css rule name)
        if re.search('((([\.\-\'])?\w+\s?\{', line):
           key = line.replace("{", "")
           di[key] = []
           line = next(file)
           while "}" not in line:
               # remove trailing whitespace and \n 
               line = ' '.join(line.split())
               di[key].append(line)
               line = next(infile)
temp.close();

source.txt

* {
    min-height: 1000px;
    overflow: hidden;
}

.leftContainerDiv {
    font-family: Helvetica;
    font-size: 10px;
    background: white;
}

#cs_ht_panel{   
    font-family:10px;
    display:block;
    font-family:Helvetica;
    width:auto;
}
//...etc

我希望输出看起来像这样(也欢迎提出可读性建议):

pythonTestFile.txt

Group 1, count(2) - font-family: Helvetica; + font-size: 10px;
Group 2: //...etc

我现在要做的是找出哪些 css 属性是重复出现的组,例如,如果 font-size: 10px 和 font-family: Helvetica 一起出现在一个规则中,那么这个组是否出现在其他任何一个组中规则以及它发生了多少次。

我不完全确定该去哪里,我什至不知道如何启动某种比较算法,或者字典是否是存储文本的正确数据结构。

编辑:回应评论,我无法使用第三方库。此脚本将在 Red Hat VM 上使用,并且只能将预先批准的软件推送到这些虚拟机上,我无法仅下载库或软件包

【问题讨论】:

  • 我不介意投反对票,但至少解释一下为什么这个问题被投反对票以便我可以解决它
  • 您的努力确实是在编写一个 css 解析器。像 tinycss (pythonhosted.org/tinycss) 这样的东西不是一个很好的起点并修改或使用它的 API 吗?
  • 我将为此问题添加更多细节,但不能选择使用第三方库。这是用于 Red Hat 虚拟机的,我们可以在这些虚拟机上使用什么软件有很大的限制。基本上我们不能下载任何第三方软件,我们使用的任何东西都是经过各种人预先批准并推送到虚拟机上的,所以我只能访问已经安装在 Red Hat 实例上的任何编程语言或工具。我同意也许手动滚动/修改/使用现有的 CSS 解析器可以使这更简单,但不幸的是它不是一个选项
  • 取决于他们的许可是什么 - 你可以从那里挑选零件并开始它。所以你基本上是手写你想要的,只是你不要从头开始,这样可以节省一些时间。即使这样,它也可能比快速解决方案要长得多
  • 您要查找的选择器将只有 2 或 3 还是一般任何 n?

标签: python sorting file-io python-2.6


【解决方案1】:

为每个 css 属性分配一个不同的素数,例如:

{
    'diplay: block': 2
    'font-size: 10px': 3,
    'font-family: Helvetica': 5,
    'min-height: 1000px': 7,
    'overflow: hidden': 11,
    'width: auto': 13,
    'background: white': 17,
}

然后创建一个字典,其中键是 css 选择器(您称之为“规则”),值是它所具有的所有属性的乘积:

{
    '#cs_ht_panel': 390, # 2 * 3 * 5 * 13
    '*': 77, # 7 * 11
    '.leftContainerDiv': 255, # 3 * 5 * 17
}

现在您可以轻松确定两件事:

  • 哪些选择器(“规则”)具有属性x(由其素数表示)或一组属性{x,y,z,..}(由它们的素数的乘积表示)通过查看选择器编号是否可被该数整除.
    例如哪些选择器同时具有'font-family: Helvetica' (5) 和font-size: 10px (3)?所有且仅能被 15 整除的那些。
  • 通过计算 GCD(最大公约数),两个选择器所具有的所有共同属性。
    例如GCD(390, 77) = 1 -> 它们没有共同的属性
    GCD(390, 255) = 15 -> 分解 -> 3 * 5

您还可以通过遍历所有选择器值来找到最常见的组,找到所有非素数的正确除数,并保留一个字典来保存已找到的除数的数量。每个除数都是一个组,你可以通过因式分解找到它的元素。

  • 390 -> 6 10 15 26 30 39 65 78 130 195
  • 255 -> 15 51 85
  • 77 ->

所以你有两次 15,其他的都是 1 次。这意味着组 15 出现了 2 次,即属性 3 和 5 的组。

最后一个计算步骤是 2^n,其中 n 是该 css 选择器中的属性数。这应该不是问题,因为大多数选择器的属性少于 10 个,但超过 20 个属性,您就会遇到麻烦。我建议通过删除前缀(moz-、webkit-)和后缀(-left、-right、-top、-bottom)来压缩属性

您可以(并且可能应该,对于具有数百行的真实 CSS 文件)仅使用集合及其操作(交集等)而不是数字、乘积和素数来完成所有这些操作;但这不是更酷吗? ;)

【讨论】:

  • 在重新阅读您的答案时,在所有 css 属性都被赋予素数的部分之后,并且选择器被分配这些乘积作为值,您将如何搜索这个?您的意思是用户会输入他们想要查看的组的素数,还是应该自动执行,以便脚本通过选择器运行并确定存在哪些组?
  • 这是我回答的最后一部分,开头是:“您还可以通过...找到最常见的组”
  • 是的,我明白了,我想我现在明白你的解决方案了。
【解决方案2】:

基于上述想法的解决方案 - 而不是使用素数 - 我使用的是集合和有序列表。可能这就是你想要的吗?

import re
import itertools

f = open('css_test.txt', 'r')
lines = f.readlines()
lines_str = ' '.join([l.strip() for l in lines])
#print lines_str

r = re.compile(r'.*?{(.*?)}') # Get All strings between {}
groups = r.findall(lines_str)
#print groups

# remove any stray spaces in the string and create groups of attributes like
# style: value
grps = []
for grp in groups:
    grps.append(filter(lambda x: len(x) > 0, grp.strip().split(';')))


# clean up those style: value attributes so that we get 'style:value'
# without any spaces and also collect all such attributes (we'd later create
# a set of these attributes)
grps2 = []
all_keys = []
for grp in grps:
    grp2 = []
    for g in grp:
        x = ':'.join([x.strip() for x in g.split(':')])
        grp2.append(x)
        all_keys.append(x)
    grps2.append(grp2)
set_keys = set(sorted(all_keys))

print set_keys
print '***********'
set_dict = {}
# For each combination of 2 of keys in the set find intersection of this
# set with the set of keys in the cleaned up groups above
# if intersection is the set of 2 keys: initialize a dictionary or add 1
for x in itertools.combinations(set_keys, 2):
    for g in grps2:
        set_x = set(x)
        set_g = set(g)
        #print "set_g : ", set_g
        if set_x  & set_g == set_x:
            print set_x
            if set_dict.has_key(x):
                set_dict[x] += 1
            else:
                set_dict[x] = 1

# print everything
print set_dict

即使此解决方案与您想要的不完全匹配 - 也许您可以按照上述思路来达到您想要做的事情?

【讨论】:

  • 我在遵循您的代码时遇到了一些问题(不是您的错,我是 Python 新手并使用集合)。您是否实质上为每个选择器创建了一个包含其属性的集合,并使用与其他所有集合的交集运算来查找公共对?
  • 简短回答 - 是的。长答案 - 商定的代码不是很易读。很多代码除了清理字符串之外什么都不做——这样我们就可以“唯一化”它们。但是让我解释一下基本思想。使用正则表达式查找我们感兴趣的“组”。现在我们处理“每个组”,对于每个组,我们获得“A-V”对。 all 这样的 A-V(作为字符串)的集合将是所有 A-V 的全局集合。然后我们从这个全局集合中创建 2 a-vs 的所有“组合”,并按组搜索它们(intersection == 2A-vs),最后将它们收集到一个出现的字典中。 HTH
猜你喜欢
  • 2021-04-25
  • 2014-11-22
  • 2010-11-19
  • 1970-01-01
  • 2023-03-25
  • 2023-01-07
  • 2019-06-07
  • 2022-01-22
  • 2012-09-14
相关资源
最近更新 更多