【问题标题】:"Difficult" sorting under some conditions在某些条件下“困难”排序
【发布时间】:2012-06-03 08:34:27
【问题描述】:

好的。这可能很难,但我已经挣扎了很多,没有很大的进步,所以我想知道你们的想法。

假设我有以下对象列表:

objects = [
        {'id': '1', 'w': 0.20}, 
        {'id': '1.1', 'w': 0.80}, 
        {'id': '1.2', 'w': 0.20},
        {'id': '1.3', 'w': 0.30},
        {'id': '1.1.1', 'w': 0.60},
        {'id': '1.1.2', 'w': 0.70},
        {'id': '1.1.3', 'w': 0.40},
        {'id': '1.2.1', 'w': 0.30},
    ]

我想按“id”(例如'1', '1.1', '1.1.1', '1.1.2', '1.1.3', '1.2', '1.2.1', '1.3')对这个列表进行排序,但是具有相同父级的所有元素都需要按“w”排序(相反)。我所说的“同父异母”是什么意思?那么,“1”是“1.1”、“1.2”和“1.3”的父级。同样,“1.1”是“1.1.1”、“1.1.2”、“1.1.3”的父级,“1.2”是“1.2.1”的父级。为了更好地说明这一点,假设这是一个带有嵌套 cmets 的线程的表示('1' 是原始帖子,'1.1' 是它的答案,等等)。

目前,我已经能够达到以下表格:

[ [ {'w': 0.2, 'id': '1'} ], [ {'w': 0.8, 'id': '1.1'}, {'w': 0.3, 'id': '1.3'}, 
{'w': 0.2, 'id': '1.2'} ], [ {'w': 0.7, 'id': '1.1.2'}, {'w': 0.6, 'id': '1.1.1'},
{'w': 0.4, 'id': '1.1.3'} ], [ {'w': 0.3, 'id': '1.2.1'} ] ]

如您所见,每个嵌套列表都包含其他元素的子元素。例如,第二个嵌套列表[ {'w': 0.8, 'id': '1.1'}, {'w': 0.3, 'id': '1.3'}, {'w': 0.2, 'id': '1.2'} ] 包含元素[ {'w': 0.2, 'id': '1'} ] 的所有子元素。此外,每个嵌套列表都按“w”排序。

最终结果应该是这样的(假设链接所有内部列表 - list(itertools.chain(*b))):

{'id': '1', 'w': 0.20}, {'id': '1.1', 'w': 0.80}, {'id': '1.1.2', 'w': 0.70}, 
{'id': '1.1.1', 'w': 0.60}, {'id': '1.1.3', 'w': 0.40}, {'id': '1.3', 'w': 0.30}, 
{'id': '1.2', 'w': 0.20},   {'id': '1.2.1', 'w': 0.30}

基本上,先是父元素,然后是子元素(按 'w' 排序),这同样适用于每个元素(当然,如果它有子元素 - 这里 {'id': '1.3', 'w': 0.30} 没有子元素,所以我们没有'不需要对它做任何事情)。

我已经尝试了一些事情(太复杂以至于不值得解释)。我最终得到了很多条件和一个丑陋的代码。

我怎样才能完成这种排序?

提前致谢。

【问题讨论】:

  • 您发布为所需结果的样本未按照您的描述进行排序。例如1.1.21.1.1之前;而w是任意排序的。
  • @BurhanKhalid w 是反向排序的,如果你仔细看,结果确实有意义。
  • @BurhanKhalid 是的,正如 jamylak 所说,1.1.2 之前是因为它是 1.1 的子代,所以根据我的描述,它们应该按“w”排序(先是 0.70,然后是 0.60)。抱歉,我错过了说明。

标签: python list sorting object


【解决方案1】:

简单的排序不会解决您的问题,因为不可能比较任何两个元素并立即知道何时出现在另一个之前(父母的权重可能会改变排序)。

需要将列表处理成树形结构,然后按顺序提取:

tree = {}

for d in objects:
    ids = d['id'].split('.')
    w = d['w']
    # walk into the tree, creating nodes as necessary
    subtree = [0,tree]
    for n in ids:
        if n not in subtree[1]:
            subtree[1][n] = [0,{}] # w, list of nodes
        subtree = subtree[1][n] # recurse
    # subtree is now the relevant node, set w
    subtree[0] = w

## now we have a tree:
## >>> pprint.pprint(tree, width=10)
## {'1': [0.2,
##       {'1': [0.8,
##              {'1': [0.6,
##                     {}],
##               '2': [0.7,
##                     {}],
##               '3': [0.4,
##                     {}]}],
##        '2': [0.2,
##              {'1': [0.3,
##                     {}]}],
##        '3': [0.3,
##              {}]}]}

# now walk the tree and extract the nodes:
result = []
def walk_subtree(subtree, path=[]):
    keyweights = [(subtree[key][0], key) for key in subtree]
    # walk through nodes at this level, outputting.
    for weight, key in sorted(keyweights, reverse=True):
        result.append(('.'.join(path + [key]), weight))
        walk_subtree(subtree[key][1], path=path+[key])

walk_subtree(tree)

##>>> pprint.pprint(result)
##[('1', 0.2),
## ('1.1', 0.8),
## ('1.1.2', 0.7),
## ('1.1.1', 0.6),
## ('1.1.3', 0.4),
## ('1.3', 0.3),
## ('1.2', 0.2),
## ('1.2.1', 0.3)]

【讨论】:

  • @jnnnnn 这看起来很棒,尽管正如我在另一个答案中所说,我避免使用树来对答案进行排序。至少,这是在不进入数据库的情况下完成的。我会在几个小时后看看你的代码(这里有点晚了)但是非常感谢你:-)。
  • @RobertSmith - 我不认为你可以在这里避免树或树状结构,因为你要做的是在一个非常基本的基于树的感知(父母/孩子/等)。
  • @jnnnnn 这个解决方案给我留下了深刻的印象。我问你,这是标准程序吗?似乎它使用字典通过引用传递来构造树的事实,同时实际修改子树。它还在子树中公开列表以更改该节点的权重,并且由于列表也是可变的,因此这些更改在树中执行。当然,您正在考虑检查节点并在它们不存在时创建它们,但这并不是那么简单。我需要仔细研究 walk_subtree 的某些部分,但正如我所说,这是非常好的解决方案。
  • 如果很难理解,那么这不是一个好的解决方案:/。一个明确的解决方案是使用一个小类来存储节点(而不是一个二元素列表)。
【解决方案2】:

使用比较器进行排序。也就是说,你写了一个看起来像的方法

def comparator(x, y):
    ## some code that sets value to -1 if x is "less than" y
    ## or 1 if x is "greater than or equal to" y.
    return value

然后打电话给objects.sort(comparator),你就应该准备好了。

这样,您一次只需要比较两个项目。只要你保持一致,这应该不是问题。

【讨论】:

  • 然后当你移动到 3.x 时它会爆炸,因为 3.x 不支持比较器。
  • @IgnacioVazquez-Abrams 并不是每个人都计划迁移到 3,尤其是因为它不必要地删除了此类功能。
  • @IgnacioVazquez-Abrams 3.x 不支持比较器是什么意思?运行所有元素所需的时间太长了?
  • @RobertSmith:不,你只需为对象写一个“小于”运算符:docs.python.org/release/3.0.1/reference/…
  • 哦,现在我明白了。我认为 3.x 意味着 3.1、3.1.1、3.2、3.2.1 等 :-)。好吧,这绝对可以,但我担心它会有点乱。此外,我真的很关心性能,因为我可以轻松达到 10.x.x(尽管 x
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-17
  • 2019-10-17
  • 1970-01-01
相关资源
最近更新 更多