Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python sort list of lists partially reverse based on date

I have a problem. I have list of lists that look something like this:

[
[datetime.date(2019, 3, 29), Decimal('44819.75')],
[datetime.date(2019, 3, 29), Decimal('45000.00')],
[datetime.date(2019, 3, 28), Decimal('0.00')],
[datetime.date(2019, 3, 22), Decimal('-275.00')],
[datetime.date(2019, 3, 22), Decimal('-350.00')],
[datetime.date(2019, 3, 22), Decimal('-175.00')]
]

I need sorting to be on date field(1st one), but each set of same dates must be sorted in reverse order. Resulting list must look like this:

[
[datetime.date(2019, 3, 29), Decimal('45000.00')],
[datetime.date(2019, 3, 29), Decimal('44819.75')],
[datetime.date(2019, 3, 28), Decimal('0.00')],
[datetime.date(2019, 3, 22), Decimal('-175.00')],
[datetime.date(2019, 3, 22), Decimal('-350.00')],
[datetime.date(2019, 3, 22), Decimal('-275.00')],
]

As you can see list is ordered by date but, for the same dates list is reversed.

dates are still descending 2019-3-29 2019-3-28 2019-3-22 but for each date, if there more than 1 element for that date, items are reversed.

for 2019-3-29 there are 2 element

[datetime.date(2019, 3, 29), Decimal('44819.75')],
[datetime.date(2019, 3, 29), Decimal('45000.00')],

and in resulting list of lists order is reversed

[datetime.date(2019, 3, 29), Decimal('45000.00')],
[datetime.date(2019, 3, 29), Decimal('44819.75')],

Unfortunately i can not find most pythonic way to do that, only ugly nested cycles

like image 985
Alexey Ignatyuk Avatar asked Mar 22 '20 09:03

Alexey Ignatyuk


2 Answers

I took the liberty to simplify the datatypes since it is easier to read this way.

# Simplified representation.
# a few random values at the start and then multiple 2's and that the current order is a,b,c
# We expect all values to be sorted on the integer part first. And that the order for the 2's is c,b,a at the end.
data = [
    [1, '-'],
    [5, '-'],
    [3, '-'],

    [2, 'a'],
    [2, 'b'],
    [2, 'c']
]


data = data[::-1]
data = sorted(data, key=lambda x:x[0])

Printing the data will yield:

[1, '-']
[2, 'c']
[2, 'b']
[2, 'a']
[3, '-']
[5, '-']

Which I believe is that you wanted.

This solution is very easy to read which has its benefits when working with others.

sorted in python is a stable sorting algorithm. This is why you if you sort normally the order of 'a b c' is preserved. Thats why reversing first works, sorted will not change the order in which equal items appeared.

Note that this also works.

data = sorted(data, key=lambda x:x[0], reverse=True)
data = data[::-1]

Here we do a reverse sort and then read the data backwards.

like image 150
Alex Telon Avatar answered Nov 03 '22 05:11

Alex Telon


An O(n) solution using itertools.groupby to group and reverse each date's items:

data = [d for _, g in groupby(data, lambda d: d[0]) for d in [*g][::-1]]

(This requires the dates to already be descending in the input, but your question, especially your "dates are still descending" sounds like that's indeed the case.)

Demo:

import datetime
from decimal import Decimal
from itertools import groupby

data = [
[datetime.date(2019, 3, 29), Decimal('44819.75')],
[datetime.date(2019, 3, 29), Decimal('45000.00')],
[datetime.date(2019, 3, 28), Decimal('0.00')],
[datetime.date(2019, 3, 22), Decimal('-275.00')],
[datetime.date(2019, 3, 22), Decimal('-350.00')],
[datetime.date(2019, 3, 22), Decimal('-175.00')]
]

data = [d for _, g in groupby(data, lambda d: d[0]) for d in [*g][::-1]]

for d in data:
    print(d)

Output:

[datetime.date(2019, 3, 29), Decimal('45000.00')]
[datetime.date(2019, 3, 29), Decimal('44819.75')]
[datetime.date(2019, 3, 28), Decimal('0.00')]
[datetime.date(2019, 3, 22), Decimal('-175.00')]
[datetime.date(2019, 3, 22), Decimal('-350.00')]
[datetime.date(2019, 3, 22), Decimal('-275.00')]
like image 28
Kelly Bundy Avatar answered Nov 03 '22 06:11

Kelly Bundy