【问题标题】:Comparing a combined start and end date to determine ordering比较组合的开始日期和结束日期以确定排序
【发布时间】:2021-12-06 18:13:54
【问题描述】:

对于始终包含 startend 日期的 Python 字典列表,您将如何根据“组合”startend 日期对列表进行排序?

使用以下标准获得最终结果的最简单(最 Pythonic)的方法是什么 - 从上到下:

  1. 首先按end_date(降序)排序,然后按start_date(降序)排序。
  2. 如果有两个对象具有相同的end_date,则最新的start_date 排在第一位,即:然后按start_date 为这些项目排序。
  3. 如果start_dateend_date 相同,那么这些项目的顺序不是问题,可以忽略或保持原样。
import datetime


blah = [
    {"id": 1, "start_date": datetime.date(2021, 5, 1), "end_date": None},
    {"id": 2, "start_date": datetime.date(2013, 2, 1), "end_date": None},
    {"id": 3, "start_date": datetime.date(2017, 1, 1), "end_date": datetime.date(2018, 1, 1)},
    {"id": 4, "start_date": datetime.date(2016, 5, 1), "end_date": datetime.date(2019, 6, 1)},
    {"id": 5, "start_date": datetime.date(2012, 1, 1), "end_date": datetime.date(2015, 1, 1)},
    {"id": 6, "start_date": datetime.date(2008, 1, 1), "end_date": datetime.date(2011, 1, 1)},
    {"id": 7, "start_date": datetime.date(2006, 1, 1), "end_date": datetime.date(2008, 1, 1)},
    {"id": 8, "start_date": datetime.date(2005, 1, 15), "end_date": datetime.date(2010, 1, 15)},
    {"id": 9, "start_date": datetime.date(2002, 1, 15), "end_date": datetime.date(2002, 1, 15)},
    {"id": 10, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
    {"id": 11, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
    {"id": 12, "start_date": datetime.date(2001, 2, 1), "end_date": datetime.date(2003, 1, 1)},
    {"id": 13, "start_date": datetime.date(2001, 1, 15), "end_date": datetime.date(2003, 1, 15)},
    {"id": 14, "start_date": datetime.date(1998, 1, 1), "end_date": datetime.date(2001, 1, 1)},
    {"id": 15, "start_date": datetime.date(1997, 1, 15), "end_date": datetime.date(1997, 1, 15)}
]

# Do something here...and return `result`.

result = [
    {"id": 1, "start_date": datetime.date(2021, 5, 1), "end_date": None},
    {"id": 2, "start_date": datetime.date(2013, 2, 1), "end_date": None},
    {"id": 4, "start_date": datetime.date(2016, 5, 1), "end_date": datetime.date(2019, 6, 1)},
    {"id": 3, "start_date": datetime.date(2017, 1, 1), "end_date": datetime.date(2018, 1, 1)},
    {"id": 5, "start_date": datetime.date(2012, 1, 1), "end_date": datetime.date(2015, 1, 1)},
    {"id": 6, "start_date": datetime.date(2008, 1, 1), "end_date": datetime.date(2011, 1, 1)},
    {"id": 8, "start_date": datetime.date(2005, 1, 15), "end_date": datetime.date(2010, 1, 15)},
    {"id": 7, "start_date": datetime.date(2006, 1, 1), "end_date": datetime.date(2008, 1, 1)},
    {"id": 11, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
    {"id": 10, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
    {"id": 9, "start_date": datetime.date(2002, 1, 15), "end_date": datetime.date(2002, 1, 15)},
    {"id": 12, "start_date": datetime.date(2001, 2, 1), "end_date": datetime.date(2003, 1, 1)},
    {"id": 13, "start_date": datetime.date(2001, 1, 15), "end_date": datetime.date(2003, 1, 15)},
    {"id": 14, "start_date": datetime.date(1998, 1, 1), "end_date": datetime.date(2001, 1, 1)},
    {"id": 15, "start_date": datetime.date(1997, 1, 15), "end_date": datetime.date(1997, 1, 15)}
]

【问题讨论】:

    标签: python date sorting dictionary list-comprehension


    【解决方案1】:

    您可以简单地使用适当的键对数据进行排序以满足第 1-3 点;第 4 点自动满足,因为 Python 中的排序保证是稳定的:

    result = sorted(blah,
                    reverse = True,
                    key=lambda d:(
                        d["end_date"] if d["end_date"] is not None else datetime.date(2999,12,31),
                        d["start_date"])
                    )
    

    【讨论】:

    • 第3点将不满足
    • 是的。您的输入中没有任何情况,因为唯一相同的结束日期是 id 1 和 2,它们的顺序已经正确,以及 id 10 和 11,它们也具有相同的开始日期。但是,如果您更改 id 1 的开始日期,请将其设置为例如在 2010 年,您会看到 id 2 将出现在 id 1 之前
    • 第 3 点只是对具有相同 end_date 的日期施加自然顺序。我做了以下测试:ids 10 和 11 具有相同的 end_date 和相同的 start_date。因此,如果您更改两者之一的start_date,那么很容易对第 3 点进行测试(请参阅我的blah 列表)。 sorted 推断 start_date 的插入顺序,而不是按值
    • 我不明白您的意思:如果您更改 id 11 的开始日期(我将其设置为 2003-05-01),那么 id 11 会出现在 id 10 之前 - 否则 10 会出现在 11 之前
    • 感谢您的回答。 @cards 的两个答案和这个都是该问题的合适人选。
    【解决方案2】:

    获得结局的最简单(最 Pythonic)的方法是什么 结果...

    我能想到的最简单(最 Pythonic)的方法是使用 pandas。

    演示:

    import datetime
    import pandas as pd
    
    blah = [
        {"id": 1, "start_date": datetime.date(2021, 5, 1), "end_date": None},
        {"id": 2, "start_date": datetime.date(2013, 2, 1), "end_date": None},
        {"id": 3, "start_date": datetime.date(2017, 1, 1), "end_date": datetime.date(2018, 1, 1)},
        {"id": 4, "start_date": datetime.date(2016, 5, 1), "end_date": datetime.date(2019, 6, 1)},
        {"id": 5, "start_date": datetime.date(2012, 1, 1), "end_date": datetime.date(2015, 1, 1)},
        {"id": 6, "start_date": datetime.date(2008, 1, 1), "end_date": datetime.date(2011, 1, 1)},
        {"id": 7, "start_date": datetime.date(2006, 1, 1), "end_date": datetime.date(2008, 1, 1)},
        {"id": 8, "start_date": datetime.date(2005, 1, 15), "end_date": datetime.date(2010, 1, 15)},
        {"id": 9, "start_date": datetime.date(2002, 1, 15), "end_date": datetime.date(2002, 1, 15)},
        {"id": 10, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
        {"id": 11, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)},
        {"id": 12, "start_date": datetime.date(2001, 2, 1), "end_date": datetime.date(2003, 1, 1)},
        {"id": 13, "start_date": datetime.date(2001, 1, 15), "end_date": datetime.date(2003, 1, 15)},
        {"id": 14, "start_date": datetime.date(1998, 1, 1), "end_date": datetime.date(2001, 1, 1)},
        {"id": 15, "start_date": datetime.date(1997, 1, 15), "end_date": datetime.date(1997, 1, 15)}
    ]
    
    df = pd.DataFrame(blah)
    
    result = df.sort_values(['end_date', 'start_date'], ascending=(False, False), na_position='first').to_dict('records')
    
    for e in result:
        print(e)
    

    输出:

    {'id': 1, 'start_date': datetime.date(2021, 5, 1), 'end_date': None}
    {'id': 2, 'start_date': datetime.date(2013, 2, 1), 'end_date': None}
    {'id': 4, 'start_date': datetime.date(2016, 5, 1), 'end_date': datetime.date(2019, 6, 1)}
    {'id': 3, 'start_date': datetime.date(2017, 1, 1), 'end_date': datetime.date(2018, 1, 1)}
    {'id': 5, 'start_date': datetime.date(2012, 1, 1), 'end_date': datetime.date(2015, 1, 1)}
    {'id': 6, 'start_date': datetime.date(2008, 1, 1), 'end_date': datetime.date(2011, 1, 1)}
    {'id': 8, 'start_date': datetime.date(2005, 1, 15), 'end_date': datetime.date(2010, 1, 15)}
    {'id': 7, 'start_date': datetime.date(2006, 1, 1), 'end_date': datetime.date(2008, 1, 1)}
    {'id': 10, 'start_date': datetime.date(2002, 1, 1), 'end_date': datetime.date(2006, 1, 1)}
    {'id': 11, 'start_date': datetime.date(2002, 1, 1), 'end_date': datetime.date(2006, 1, 1)}
    {'id': 13, 'start_date': datetime.date(2001, 1, 15), 'end_date': datetime.date(2003, 1, 15)}
    {'id': 12, 'start_date': datetime.date(2001, 2, 1), 'end_date': datetime.date(2003, 1, 1)}
    {'id': 9, 'start_date': datetime.date(2002, 1, 15), 'end_date': datetime.date(2002, 1, 15)}
    {'id': 14, 'start_date': datetime.date(1998, 1, 1), 'end_date': datetime.date(2001, 1, 1)}
    {'id': 15, 'start_date': datetime.date(1997, 1, 15), 'end_date': datetime.date(1997, 1, 15)}
    

    【讨论】:

    • 感谢 Arvind Kumar Avinash 的精彩回答。虽然是正确的,但我们接受其他答案的组合只是因为它们不依赖于导入任何外部库,即:pandas。 (我们特别不想限制对这个问题使用外部库,这就是为什么我们没有在问题中提到这个限制)。我们相信这对那些使用 pandas 的人会有用!
    【解决方案3】:

    要订购 wrt end_date,我引入了“假日期”以使数据保持一致。此选择是任意的,但应避免与其他值发生冲突。内置函数sortedreversed需要一个具有同质数据的迭代器,所以没有None

    sorted 返回一个列表,reversed 一个生成器。

    # In order to make sense the question 3. I modify the start date for 2006, see comment
    blah = [
        {"id": 1, "start_date": datetime.date(2021, 5, 1), "end_date": None},
        {"id": 2, "start_date": datetime.date(2013, 2, 1), "end_date": None},
        {"id": 3, "start_date": datetime.date(2017, 1, 1), "end_date": datetime.date(2018, 1, 1)},
        {"id": 4, "start_date": datetime.date(2016, 5, 1), "end_date": datetime.date(2019, 6, 1)},
        {"id": 5, "start_date": datetime.date(2012, 1, 1), "end_date": datetime.date(2015, 1, 1)},
        {"id": 6, "start_date": datetime.date(2008, 1, 1), "end_date": datetime.date(2011, 1, 1)},
        {"id": 7, "start_date": datetime.date(2006, 1, 1), "end_date": datetime.date(2008, 1, 1)},
        {"id": 8, "start_date": datetime.date(2005, 1, 15), "end_date": datetime.date(2010, 1, 15)},
        {"id": 9, "start_date": datetime.date(2002, 1, 15), "end_date": datetime.date(2002, 1, 15)},
        {"id": 10, "start_date": datetime.date(2002, 1, 2), "end_date": datetime.date(2006, 1, 1)}, # <---- modified start_date!
        {"id": 11, "start_date": datetime.date(2002, 1, 1), "end_date": datetime.date(2006, 1, 1)}, 
        {"id": 12, "start_date": datetime.date(2001, 2, 1), "end_date": datetime.date(2003, 1, 1)},
        {"id": 13, "start_date": datetime.date(2001, 1, 15), "end_date": datetime.date(2003, 1, 15)},
        {"id": 14, "start_date": datetime.date(1998, 1, 1), "end_date": datetime.date(2001, 1, 1)},
        {"id": 15, "start_date": datetime.date(1997, 1, 15), "end_date": datetime.date(1997, 1, 15)}
    ]
    

    这里是代码。

    import itertools as it
    import datetime
    
    FAKE_DATE = datetime.date(2999, 9, 9) # or any non-interfering date 
    
    # 1
    print(sorted(blah, key=lambda p: p['start_date']))
    
    print(sorted(blah, reverse=True, key=lambda p: p['start_date'])) # reverse, A
    print(list(reversed(sorted(blah, key=lambda p: p['start_date'])))) # reverse, B
    
    # 2
    order_2 = reversed(sorted(blah, key=lambda p: p['end_date'] if p['end_date'] is not None else FAKE_DATE))
    
    print(list(order_2))
    
    # 3
    grp_by_end_dates = it.groupby(sorted(blah, key=lambda p: p['end_date'] if p['end_date'] is not None else FAKE_DATE), key=lambda p: p['end_date'])
    order_3 = it.chain(*(sorted(list(i), reverse=True, key=lambda p: p['start_date']) for _, i in grp_by_end_dates))
    
    print(list(order_3)
    

    【讨论】:

    • @The Go Company 我不确定 4. 以及 1 和 3 之间的区别
    • 我喜欢你的@cards 和@gimix 两种方法,因为它不需要任何额外的外部库,例如:pandas。为了澄清第 1 点和第 2 点之间的区别(为清楚起见,编辑了原始问题),默认情况下,排序是 end_date 降序(最新的优先)。但是,如果end_date 相同,则对于这些项目,默认排序应为start_date 降序(最新的优先)。第 3 点简单地描述了如果 start_dateend_date 相同,那么这些项目的顺序不是问题,可以忽略或保持原样。
    • 感谢您的回答。 @gimix 答案和这个答案都是该问题的合适人选。
    猜你喜欢
    • 2019-07-08
    • 1970-01-01
    • 2012-12-03
    • 2017-02-08
    • 1970-01-01
    • 1970-01-01
    • 2023-03-17
    • 1970-01-01
    • 2019-10-04
    相关资源
    最近更新 更多