Сортировка списка списков по дате и первому элементу

#python #sorting

Вопрос:

 ArrayData = [['a', 'ad', '02/10/2021  7:39:19 am', 'Rank:1'],
             ['b', 'db', '02/10/2021 6:25:20 am', 'Rank:2'],
             ['a', 'sd', '02/10/2021  5:39:19 am', 'Rank:3'],
             ['b', 'esas', '02/10/2021 6:25:20 am', 'Rank:1'],
             ['a', 'aser', '02/10/2021  9:39:19 am', 'Rank:2'],
             ['d', 'ssss', '02/10/2021  11:39:19 am', 'Rank:1']]
 

Сценарий должен

  1. Отсортируйте одну и ту же группу (например, сначала отсортируйте группу «a», затем группу «b», «c», «d») по времени. Более поздние времена и даты имеют более высокие ранги.
  2. Обновите «ранг» в каждом подмассиве

Ожидаемый результат:

 [['d', 'ssss', '02/10/2021  11:39:19 am', 'Rank:1'],
 ['b', 'esas', '03/10/2021 6:25:20 am', 'Rank:2'],
 ['b', 'db', '02/10/2021 6:25:20 am', 'Rank:1'],
 ['a', 'aser', '02/10/2021  9:39:19 am', 'Rank:3'],
 ['a', 'ad', '02/10/2021  7:39:19 am', 'Rank:2'],
 ['a', 'sd', '02/10/2021  5:39:19 am', 'Rank:1']]
 

Это текущий сценарий, который я написал

 import operator
result = sorted(ArrayData, key=operator.itemgetter(2), reverse=True)
print(result)
 

Могу я узнать, как его улучшить?

Комментарии:

1. Вы все еще сортируете даты на основе необработанных строк, вам нужно их проанализировать

2. Почему ['d', 'ssss', '11-04-20', 'Rank:1'] первый в выводе?

3. Обновите «ранг» в каждом подмассиве — можете ли вы это объяснить? (На данный момент — мое решение увеличивает ранг на 1)

4. Это довольно ясно при проверке желаемого результата: «ранг» соответствует тому, насколько недавняя дата в каждой группе. Более поздние даты имеют более высокие ранги, но каждая группа ранжируется только с другими участниками группы.

5. @ddjohn, да, это то, чего я хочу

Ответ №1:

Обратите внимание, что это преобразует ваши строки даты и времени в datetime.datetime объекты. Это может быть или не быть желательным, но, по крайней мере, рекомендуется, если вы планируете выполнять какие-либо другие операции, связанные с этими датами. Если вы действительно хотите, чтобы они были строками, см. прокомментированную строку кода.

Обратите также внимание, что я предполагаю, что ваши даты таковы dd/mm/yyyy . Если они mm/dd/yyyy вместо этого, вам нужно будет включить %d и %m DATETIME_FORMAT включить .

 import datetime
import itertools
from operator import itemgetter as get


# Assumes day/month/year, switch %d and %m if not
DATETIME_FORMAT = "%d/%m/%Y %I:%M:%S %p"


def parse_datetimes(data: list) -> list:
    result = []
    for first, second, timestamp, rank in data:
        timestamp = datetime.datetime.strptime(timestamp, DATETIME_FORMAT)
        result.append([first, second, timestamp, rank])
    return result


def custom_sort(data: list) -> list:
    # Convert datetime strings to datetime objects, then sort by first element
    sorted_data = sorted(parse_datetimes(data), key=get(0), reverse=True)

    # Re-rank each group sorted by date
    result = []
    for _, group in itertools.groupby(sorted_data, key=get(0)):
        ranked_group = []
        sorted_group = sorted(group, key=get(2))
        for rank, (*item, _) in enumerate(sorted_group, 1):
            # item[2] = item[2].strftime(DATETIME_FORMAT)
            ranked_group.append([*item, f"Rank:{rank}"])
        result.extend(ranked_group[::-1])
    return result
 

ДЕМОНСТРАЦИЯ:

 >>> custom_sort(ArrayData)
[['d', 'ssss', datetime.datetime(2021, 10, 2, 11, 39, 19), 'Rank:1'],
 ['b', 'esas', datetime.datetime(2021, 10, 2, 6, 25, 20), 'Rank:2'],
 ['b', 'db', datetime.datetime(2021, 10, 2, 6, 25, 20), 'Rank:1'],
 ['a', 'aser', datetime.datetime(2021, 10, 2, 9, 39, 19), 'Rank:3'],
 ['a', 'ad', datetime.datetime(2021, 10, 2, 7, 39, 19), 'Rank:2'],
 ['a', 'sd', datetime.datetime(2021, 10, 2, 5, 39, 19), 'Rank:1']]
 

Комментарии:

1. Что делать, если нужно учитывать как время (час/минуту), так и дату?

2. @liuxu смотрите мою правку

Ответ №2:

Другие решения казались мне слишком сложными. Если вы хотите, вы можете использовать сравнение строк, не прибегая к datetime s.

Вот пользовательское решение, которое я собрал вместе, которое, похоже, дает желаемый результат:

 from collections import defaultdict
from pprint import pprint
from typing import DefaultDict, List


array_data = [['d', 'ssss', '11-04-20', 'Rank:1'],
              ['a', 'ad', '10-13-20', 'Rank:1'],
              ['b', 'db', '12-13-20', 'Rank:2'],
              ['a', 'sd', '05-13-20', 'Rank:3'],
              ['b', 'esas', '12-14-20', 'Rank:1'],
              ['a', 'aser', '12-13-20', 'Rank:2']]


final_array = []
group_to_data: DefaultDict[str, List[List[str]]] = defaultdict(list)

for data in array_data:
    group_to_data[data[0]].append(data)


def sort_fn(x):
    """Sort by group, then by date"""
    month, day, year = x[2].split('-')
    return f'{year}{month}{day}'


for _, data in sorted(group_to_data.items(), reverse=True):
    # sorts sub-list for each group
    data.sort(key=sort_fn)

    # iterating over data in reverse order, since that's how we want it in
    # final result
    for i in range(len(data) - 1, -1, -1):
        new_rank = f"Rank:{i   1}"
        item = data[i]
        item[-1] = new_rank
        final_array.append(item)

pprint(final_array)
 

Комментарии:

1. Подсписки уже сгруппированы по первому элементу через ваш defaultdict . Я не думаю, что возврат x[0] из sort_fn() необходим, так sort_fn() как уже выполняется только для данных, сгруппированных по первому элементу. Я бы также посоветовал for _, data , если вы не планируете использовать переменную group итератора в любом месте тела цикла.

2. ах, хороший улов по обоим пунктам! в ближайшее время я обновлю ответ с этими исправлениями.