如何改进这个冗长的Python代码?

时间:2010-08-16 09:17:14

标签: python refactoring

我有这样的数据结构:

items = [
    ['Schools', '', '', '32'],
    ['Schools', 'Primary schools', '', '16'],
    ['Schools', 'Secondary schools', '', '16'],
    ['Schools', 'Secondary schools', 'Special ed', '8'],
    ['Schools', 'Secondary schools', 'Non-special ed', '8'],
]

这是一个消费项目清单。有些是聚合,例如items[0]是所有学校的总支出,items[2]是中学的总支出。那些不是聚合的是items[1], items[3]items[4]

如何优雅地减少列表,使其仅显示非聚合项目?在伪代码中:

for each item in items
   check if item[1] is blank, if it is
       check if item[0] matches another item’s[0]
       if it does and if that item’s[1] isn’t blank
           delete item
   check if item[2] is blank, if it is
       check if item[1] matches another item’s[1]
       if it does and if if that item’s[2] isn’t blank
           delete item 

到目前为止,这是我的(跛脚!)尝试:

for i in range(len(items)):
    i -= 1
    if items[i]:
        if items[i][1] == "":
            for other_item in items:
                if items[i][0]==other_item[0] and other_item[1]!="":
                    items_to_remove.append(i)
                    continue
        elif items[i][2]=="":
            for other_item in items:
                if items[i][1] == other_item[1] and other_item[2] != "":
                    items_to_remove.append(i)
                    continue
new_items = [ key for key,_ in groupby(items_to_remove)]
new_items.sort(reverse=True)  
for number in new_items:
    temp_item = items[number]
    items.remove(temp_item)

这太丑了。我能做得更好吗?

注意:我可以使用词典而不是列表,如果这样可以让生活更轻松:)

7 个答案:

答案 0 :(得分:2)

首先,我建议您的数据结构应该更像这样:

items = [
    ['Schools', None, None, 32],
    ['Schools', 'Primary schools', None, 16],
    ['Schools', 'Secondary schools', None, 8],
    ['Schools', 'Secondary schools', 'Special ed', 4],
    ['Schools', 'Secondary schools', 'Non-special ed', 4],
]

我们可以将它们分类为这样的字典:

result = {}

for item in items:
    if not item[0] in result or not isinstance(result[item[0]], dict): result[item[0]] = {}
    if not item[1] in result[item[0]] or not isinstance(result[item[0]][item[1]], dict): result[item[0]][item[1]] = {}
    if not item[2] in result[item[0]][item[1]] or not isinstance(result[item[0]][item[1]][item[2]], dict): result[item[0]][item[1]][item[2]] = {}

    if not item[0]:
        result = item[3]
    elif not item[1]:
        result[item[0]] = item[3]
    elif not item[2]:
        result[item[0]][item[1]] = item[3]
    else:
        result[item[0]][item[1]][item[2]] = item[3]

你应该最终得到一个字典:

result = {
    'Schools': {
        'Secondary schools': {
            'Non-special ed': '4',
            'Special ed': '4'
        },
        'Primary schools': '16'
    }
}

我的例程可能会被优化并递归。

此外,这些数字总计为24 - 这是您的错误吗?

答案 1 :(得分:1)

list_keys = [ "".join(x[:-1]) for x in items ]
for i in range(len(list_keys)-1):
  if not list_keys[i+1].startswith(list_keys[i]):
     print items[i]
print items[-1]

在这里,我找到每个项目的“关键字”,它是项目中的所有条目,连接在一起,除了最后一个值。

聚合项目的密钥始终是后续项目密钥的前缀,因此我们可以使用此测试来检测聚合项目并将其解除。

这个alg。打印(在您的输入上):

['Schools', 'Primary schools', '', '16']
['Schools', 'Secondary schools', 'Special ed', '4']
['Schools', 'Secondary schools', 'Non-special ed', '4'],

注意:
这假设所有项目都以树形结构整齐排列(作为原始数据)。如果不是,它会(稍微)更复杂,因为你必须在循环之前对键进行排序(并跟踪哪个键属于哪个项目)。

答案 2 :(得分:0)

如何制作物品对象?

class School (object):
    __init__(self, is_aggregate=false):
        self.is_aggregate = is_aggregate

答案 3 :(得分:0)

我正在使用

的辅助功能
  • 制作一组包含更细粒度数据的条目
  • 返回适合filter删除聚合项目的函数

通过此函数运行示例数据,它将提供所需的输出:)

def remove_aggregates(items):

    def mk_pred(index_i, blank_i, items):
        posts = set(x[index_i] for x in items if x[blank_i] != '')    
        def pred(item):
            return not (item[blank_i] == '' and item[index_i] in posts)
        return pred    

    items = filter(mk_pred(0,1,items), items)
    items = filter(mk_pred(1,2,items), items)
    return items

答案 4 :(得分:0)

你已经问过如何优雅地做,以及如何更好地做到这一点。你的nota bene暗示你正在使用的结构仍具有可塑性。如果您希望能够更优雅地这样做,我建议您更改数据的存储方式。一些选项是:

在每个列表中包含一个附加字段,指示它是否为汇总值:

items = [
    ['Schools', '', '', '32', True],
    ['Schools', 'Primary schools', '', '16', False],
    ['Schools', 'Secondary schools', '', '8', True],
    ['Schools', 'Secondary schools', 'Special ed', '4', False],
    ['Schools', 'Secondary schools', 'Non-special ed', '4', False],
]

将您的数据拆分为两个列表:

items = [
    [
        ['Schools', '', '', '32'],
        ['Schools', 'Secondary schools', '', '8'],
    ],
    [
        ['Schools', 'Primary schools', '', '16'],
        ['Schools', 'Secondary schools', 'Special ed', '4'],
        ['Schools', 'Secondary schools', 'Non-special ed', '4'],
    ],
]

使聚合值包含他们孩子的列表(尽管这仍然不是很有趣):

items = [
    ['Schools', '', '', '32', [
        ['Schools', 'Primary schools', '', '16', []],
        ['Schools', 'Secondary schools', '', '8', [
            ['Schools', 'Secondary schools', 'Special ed', '4'],
            ['Schools', 'Secondary schools', 'Non-special ed', '4'],
        ],
    ],
]

我会说你数据的当前结构不允许你用它做任何优雅的事情。您需要的逻辑是“如果此索引为空但不是另一个条目在另一个索引上具有相同值”,并且每个列表条目必须执行两次,因为该逻辑可以在两对独立的索引位置。修复存储信息的方式,您将能够编写一种减少数据的优雅方法。

例如,如果您使用我列出的第一个选项(使用布尔值来指示条目是否为聚合),您可以使用以下内容缩小列表:

reduced = [item for item in items where item[4] == False]

答案 5 :(得分:0)

此尝试尝试不依赖于输入的排序:

items = [
    ['Schools', '', '', '32'],
    ['Schools', 'Primary schools', '', '16'],
    ['Schools', 'Secondary schools', '', '16'],
    ['Schools', 'Secondary schools', 'Special ed', '8'],
    ['Schools', 'Secondary schools', 'Non-special ed', '8'],
]

def path(item,upto=None):
    return ".".join([p for p in item[:-1] if p][:upto])    

from collections import defaultdict
children_counter = defaultdict(int)
for i in items:
    children_counter[path(i,-1)] += 1

for i in items:
   if children_counter[path(i)] == 0:
        print i

答案 6 :(得分:-1)

您可以使用列表推导,如下所示:

items = [a for a in items if a[1] != '' and a[2] != '']

或者,如果任何位置的空字符串表示聚合项:

items = [a for a in items if '' not in a]

当然,您不一定需要将简化列表分配给项目 - 您可以随意使用它。