How I want to order my list:
- isRegular = True
- bold = True
- italic = True
- 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).