【问题标题】:Sort python dictionary keys based on sub-dictionary keys by defining sorting order通过定义排序顺序,根据子字典键对 python 字典键进行排序
【发布时间】:2018-08-13 17:52:42
【问题描述】:

我的问题是another question 的扩展,其中 OP 有一个字典,下面是一个字典,并希望根据子字典键对主键进行排序

myDict = {
    'SER12346': {'serial_num': 'SER12346', 'site_location': 'North America'},
    'ABC12345': {'serial_num': 'ABC12345', 'site_location': 'South America'},
    'SER12345': {'serial_num': 'SER12345', 'site_location': 'North America'},
    'SER12347': {'serial_num': 'SER12347', 'site_location': 'South America'},
    'ABC12346': {'serial_num': 'ABC12346', 'site_location': 'Europe'}
}

建议的(引用如下)解决方案完美运行。

dicts = myDict.items()
dicts.sort(key=lambda (k,d): (d['site_location'], d['serial_num'],))

但是,此解决方案对所有内容进行升序排序(降序排序很简单)。我想知道是否可以定义混合排序顺序,比如serial_num 升序,site_location 降序?

【问题讨论】:

  • @faith_diur,以下解决方案有帮助吗?随意接受一个(左侧的绿色勾号),或提出进一步的问题以进行澄清。

标签: python sorting dictionary


【解决方案1】:

这是一种可能的解决方案,因为列表排序是stable。我稍微更改了数据以证明它有效。

Python documentation中也规定了这种方法。

myDict = {
    'SER12346': {'serial_num': 'SER12346', 'site_location': 'North America'},
    'ABC12346': {'serial_num': 'ABC12345', 'site_location': 'Europe'},
    'ABC12345': {'serial_num': 'ABC12345', 'site_location': 'South America'},
    'SER12345': {'serial_num': 'SER12345', 'site_location': 'North America'},
    'SER12347': {'serial_num': 'SER12347', 'site_location': 'South America'}
}

result = sorted(myDict.items(), key=lambda x: x[1]['site_location'], reverse=True)
result = sorted(result, key=lambda x: x[1]['serial_num'])

# [('ABC12345', {'serial_num': 'ABC12345', 'site_location': 'South America'}),
#  ('ABC12346', {'serial_num': 'ABC12345', 'site_location': 'Europe'}),
#  ('SER12345', {'serial_num': 'SER12345', 'site_location': 'North America'}),
#  ('SER12346', {'serial_num': 'SER12346', 'site_location': 'North America'}),
#  ('SER12347', {'serial_num': 'SER12347', 'site_location': 'South America'})]

【讨论】:

  • 这比我将元组子类化并重新实现丰富的比较方法以对第二个元素进行反向排序的计划要容易得多......哦,是的,稳定排序很有用。
  • 我仍然希望看到美观的一次性解决方案。
  • 我不确定“审美”,但我只是发布了一个一次性解决方案。
  • 我是 OOP 解决方案的忠实拥护者。它们在 python 中的使用严重不足。
  • 感谢您的解决方案,我可以看到它是原始问题答案的一个很好的变体。 @AdamSmith,很可能我是在 python 中未充分使用 OOP 的贡献者之一。
【解决方案2】:

如果您确实需要自定义排序顺序,那么您可以使用该排序逻辑编写一个自定义对象,该对象将用作下面实际对象的包装器。

from functools import total_ordering
# total_ordering keeps you from having to write each of
# __gt__, __ge__, __lt__, __le__. It requires __eq__ and one of the
# other comparison functions to be defined and the rest are assumed
# in terms of each other.  (__ge__ = __gt__ or __eq__, __gt__ = not __le__), etc.

@total_ordering
class CustomSorter(object):
    def __init__(self, data):
        self.data = data

    # the properties here are solely to make the code a little more readable
    # in the rich comparators below. You can ignore them if you like.
    @property
    def serial_number(self):
        return self.data[1]["serial_number"]
    @property
    def site_location(self):
        return self.data[1]["site_location"]

    def __eq__(self, other):
        if not isinstance(other, CustomSorter):
            raise NotImplementedError("CustomSorters can only sort with themselves")
        return self.data[1] == other.data[1]

    def __lt__(self, other):
        if not isinstance(other, CustomSorter):
            raise NotImplementedError("CustomSorters can only sort with themselves")
        if self.site_location == other.site_location:
            return self.site_number < other.site_number
        else:
            return not (self.site_location < other.site_location)

然后使用传统的装饰-排序-取消装饰步骤。

myDict = {
    'SER12346': {'serial_num': 'SER12346', 'site_location': 'North America'},
    'ABC12345': {'serial_num': 'ABC12345', 'site_location': 'South America'},
    'SER12345': {'serial_num': 'SER12345', 'site_location': 'North America'},
    'SER12347': {'serial_num': 'SER12347', 'site_location': 'South America'},
    'ABC12346': {'serial_num': 'ABC12346', 'site_location': 'Europe'}
}
sorters = [CustomSorter(tup) for tup in myDict.items()]
sorters.sort()
result = [sorter.data for sorter in sorters]

如果你实现一个函数来为你排序,这可能是最好的。

def sort_on(sorter, unsorted):
    """sort_on expects @sorter@ to be a class with rich comparison operations
    that is a decorative wrapper around some data to be sorted. Additionally,
    @sorter.data@ should refer to the underlying data structure.
    """

    decorated = [sorter(unsort) for unsort in unsorted]
    decosorted = decorated.sort()
    sorted = [decosort.data for decosort in decosorted]
    return sorted

result = sort_on(CustomSorter, myDict.items())

【讨论】:

  • 感谢您的贡献和努力,这是一种更优雅的一次性解决方案。然而,答案远比这更实际,至少在这个问题的背景下,我认为。
【解决方案3】:

我认为您使用元组的单一排序方法是最 Pythonic 的,所以我会坚持这一点。如果值都是数字,您可以轻松地否定任何元组值以获得键的该部分的相反顺序,但这里的问题是您想要否定字符串,对吗?所以让我们解决这个问题:

def str_to_neg_ords(s):
    return tuple(-ord(c) for c in s)

现在您可以使用此函数作为键的一部分,进行嵌套字典排序:

sorted(myDict.values(),
       key=lambda d: (str_to_neg_ords(d['site_location']), d['serial_num']))

【讨论】:

    猜你喜欢
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    • 2015-08-09
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多