排序列表多个键和最近的值

Sort list multiple key and upper nearest value

我想如何排序我的列表:

  1. isRegular = True
  2. 粗体 = 正确
  3. 斜体 = 正确
  4. 最近的权重上限值。在我的示例中,我想要最接近 400 的值。

我的列表包含这些 NamedTuple:

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

这是我现在的排序方式(不太好)

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

输入

[
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),
]

我目前的输出(如果我多次尝试 运行 代码,它会给我不同的输出。我不知道为什么会这样。这是我可以获得的 2 个输出示例)

# 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),
]

这是我想要的输出

[
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)
]

list.sort() 默认为升序。你不需要做 -abs(400 - font.weight)。去掉前面的负号即可:

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

注意:对于300和500的情况(与400的距离相等),300会排在第一位,因为它是按升序排列的。如果你想要反过来,在元组中包含 -font.weight 像这样:

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

定义一个old-style比较函数,functools.cmp_to_key会变成一个合适的键函数。

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 至少可以说有点冗长。您可能想根据 Python 2 的旧 cmp 函数来定义它,在这里简单地重新定义。

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)))

目标权重参数化后,可以使用

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

你也可以考虑一个只需要权重的工厂函数:

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

使用比较函数可以让您更精确地 per-field 排序,而不必求助于否定技巧(无论如何只适用于数字字段)。