Home > front end >  Sort list multiple key and upper nearest value
Sort list multiple key and upper nearest value

Time:04-15

How I want to order my list:

  1. isRegular = True
  2. bold = True
  3. italic = True
  4. The upper nearest value of weight. In my example, I want the nearest value to 400.

My list contains these NamedTuple:

class Font(NamedTuple):
    fontPath: str
    fontName: str
    isRegular: bool
    bold: bool
    italic: bool
    weight: int

Here is now how I sorted it (which is not good)

fontMatch.sort(key=lambda font: (-font.isRegular, -font.bold, -font.italic, -abs(400 - font.weight)))

Input

[
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Thin_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=250),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Light_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=300),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Medium_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=500),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Regular_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=True, bold=False, italic=False, weight=400),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY ExtraBold_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=800),
Font(fontPath='C:\\Windows\\Fonts\\Deleted\\MADE TOMMY BOLD_PERSONAL USE.OTF', fontName='MADE TOMMY', isRegular=False, bold=True, italic=False, weight=700),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Black_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=900),
]

Output I currently have (if I try to run the code multiple time, it will give me different output. I have no idea why it does that. Here is 2 output example I can get)

# Output 1
[
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Regular_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=True, bold=False, italic=False, weight=400),
Font(fontPath='C:\\Windows\\Fonts\\Deleted\\MADE TOMMY BOLD_PERSONAL USE.OTF', fontName='MADE TOMMY', isRegular=False, bold=True, italic=False, weight=700),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Thin_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=250),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Light_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=300),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Black_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=900),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Medium_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=500),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY ExtraBold_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=800)
]

# Output 2
[
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Regular_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=True, bold=False, italic=False, weight=400),
Font(fontPath='C:\\Windows\\Fonts\\Deleted\\MADE TOMMY BOLD_PERSONAL USE.OTF', fontName='MADE TOMMY', isRegular=False, bold=True, italic=False, weight=700),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Black_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=900),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY ExtraBold_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=800),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Thin_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=250),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Medium_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=500),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Light_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=300),
]

Here is the output I want

[
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Regular_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=True, bold=False, italic=False, weight=400),
Font(fontPath='C:\\Windows\\Fonts\\Deleted\\MADE TOMMY BOLD_PERSONAL USE.OTF', fontName='MADE TOMMY', isRegular=False, bold=True, italic=False, weight=700),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Medium_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=500),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Light_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=300),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Thin_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=250),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY ExtraBold_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=800),
Font(fontPath='C:\\Windows\\Fonts\\MADE TOMMY Black_PERSONAL USE.otf', fontName='MADE TOMMY', isRegular=False, bold=False, italic=False, weight=900)
]

CodePudding user response:

list.sort() is ascending by default. You don't need to do -abs(400 - font.weight). Just remove the negative sign at the front:

fontMatch.sort(key=lambda font: (-font.isRegular, -font.bold, -font.italic, abs(400 - font.weight)))

Note: For the case of 300 and 500 (equal distance from 400), the 300 will be placed first since it is sorted in ascending order. If you want it the other way round, include -font.weight in the tuple like so:

fontMatch.sort(key=lambda font: (-font.isRegular, -font.bold, -font.italic, abs(400 - font.weight), -font.weight))

CodePudding user response:

Define an old-style comparison function, which functools.cmp_to_key will turn into an appropriate key function.

from functools import cmp_to_key

def compare(f1, f2):
    if f1.isRegular and f2.isRegular:
        if f1.bold and f2.bold:
            if f1.italic and f2.italic:
                d1 = abs(f1.weight - 400)
                d2 = abs(f2.weight - 400)
                return f1 if d1 <= d2 else f2
            else:
                return f1 if f1.italic else f2
        else:
            return f1 if f1.bold else f2
    else:
        return f1 if f2.bold else f2


fontMatch.sort(key=cmp_to_key(compare))

compare is a bit verbose, to say the least. You might want to define it in in terms of Python 2's old cmp function, redefined trivially here.

def cmp(x, y):
    return -1 if x < y else 0 if x == y else 1

def compare(f1, f2, w=400):
    # Since False < True, we swap the order of the arguments
    # for the boolean fields. 
    return (cmp(f2.isRegular, f1.isRegular)
            or cmp(f2.bold, f1.bold)
            or cmp(f2.italic, f1.italic)
            or cmp(abs(f1.weight - w), abs(f2.weight - w)))

With the target weight parameterized, you can use

fontMatch.sort(key=cmp_to_key(compare))
fontMatch.sort(key=cmp_to_key(lambda x, y: compare(x,y,700))
# etc

You might also consider a factory function that just takes a weight:

def make_compare(w=400):
    def compare(x, y):
        return (cmp(f2.isRegular, f1.isRegular)
                or cmp(f2.bold, f1.bold)
                or cmp(f2.italic, f1.italic)
                or cmp(abs(f1.weight - w), abs(f2.weight - w)))


fontMatch.sort(key=cmp_to_key(make_compare()))
fontMatch.sort(key=cmp_to_key(make_compare(700)))
# etc

Using a comparison function lets you be more precise about per-field ordering without having to resort to things like the negation trick (which only works for numerical fields anyway).

  • Related