วิธีจัดเรียงวัตถุโดยใช้ปุ่มหลายปุ่มใน Python


97

หรือในทางปฏิบัติฉันจะจัดเรียงรายการพจนานุกรมด้วยปุ่มหลายปุ่มได้อย่างไร

ฉันมีรายการคำสั่ง:

b = [{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 96.0},
 {u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0},
 {u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0},
 {u'TOT_PTS_Misc': u'Foster, Toney', u'Total_Points': 80.0},
 {u'TOT_PTS_Misc': u'Lawson, Roman', u'Total_Points': 80.0},
 {u'TOT_PTS_Misc': u'Lempke, Sam', u'Total_Points': 80.0},
 {u'TOT_PTS_Misc': u'Gnezda, Alex', u'Total_Points': 78.0},
 {u'TOT_PTS_Misc': u'Kirks, Damien', u'Total_Points': 78.0},
 {u'TOT_PTS_Misc': u'Worden, Tom', u'Total_Points': 78.0},
 {u'TOT_PTS_Misc': u'Korecz, Mike', u'Total_Points': 78.0},
 {u'TOT_PTS_Misc': u'Swartz, Brian', u'Total_Points': 66.0},
 {u'TOT_PTS_Misc': u'Burgess, Randy', u'Total_Points': 66.0},
 {u'TOT_PTS_Misc': u'Smugala, Ryan', u'Total_Points': 66.0},
 {u'TOT_PTS_Misc': u'Harmon, Gary', u'Total_Points': 66.0},
 {u'TOT_PTS_Misc': u'Blasinsky, Scott', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Carter III, Laymon', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Coleman, Johnathan', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Venditti, Nick', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Blackwell, Devon', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Kovach, Alex', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Bolden, Antonio', u'Total_Points': 60.0},
 {u'TOT_PTS_Misc': u'Smith, Ryan', u'Total_Points': 60.0}]

และฉันต้องใช้คีย์หลายเรียงตรงกันข้าม Total_Points TOT_PTS_Miscแล้วไม่ได้ตรงกันข้าม

สามารถทำได้ที่พรอมต์คำสั่งดังนี้:

a = sorted(b, key=lambda d: (-d['Total_Points'], d['TOT_PTS_Misc']))

แต่ฉันต้องเรียกใช้สิ่งนี้ผ่านฟังก์ชั่นโดยที่ฉันส่งผ่านรายการและคีย์การเรียงลำดับ ตัวอย่างเช่นdef multikeysort(dict_list, sortkeys):.

จะใช้เส้นแลมบ์ดาได้อย่างไรซึ่งจะจัดเรียงรายการสำหรับจำนวนคีย์โดยพลการที่ส่งผ่านไปยังฟังก์ชันหลายประเภทและคำนึงถึงว่าคีย์เรียงลำดับอาจมีจำนวนคีย์เท่าใดก็ได้และจะมีการระบุประเภทที่ต้องย้อนกลับ ด้วยเครื่องหมาย "-" ก่อนหน้านี้?

คำตอบ:


73

คำตอบนี้ใช้ได้กับคอลัมน์ประเภทใดก็ได้ในพจนานุกรม - คอลัมน์เชิงลบไม่จำเป็นต้องเป็นตัวเลข

def multikeysort(items, columns):
    from operator import itemgetter
    comparers = [((itemgetter(col[1:].strip()), -1) if col.startswith('-') else
                  (itemgetter(col.strip()), 1)) for col in columns]
    def comparer(left, right):
        for fn, mult in comparers:
            result = cmp(fn(left), fn(right))
            if result:
                return mult * result
        else:
            return 0
    return sorted(items, cmp=comparer)

เรียกแบบนี้ก็ได้ว่า

b = [{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 96.0},
     {u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0},
     {u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0},
     {u'TOT_PTS_Misc': u'Foster, Toney', u'Total_Points': 80.0},
     {u'TOT_PTS_Misc': u'Lawson, Roman', u'Total_Points': 80.0},
     {u'TOT_PTS_Misc': u'Lempke, Sam', u'Total_Points': 80.0},
     {u'TOT_PTS_Misc': u'Gnezda, Alex', u'Total_Points': 78.0},
     {u'TOT_PTS_Misc': u'Kirks, Damien', u'Total_Points': 78.0},
     {u'TOT_PTS_Misc': u'Worden, Tom', u'Total_Points': 78.0},
     {u'TOT_PTS_Misc': u'Korecz, Mike', u'Total_Points': 78.0},
     {u'TOT_PTS_Misc': u'Swartz, Brian', u'Total_Points': 66.0},
     {u'TOT_PTS_Misc': u'Burgess, Randy', u'Total_Points': 66.0},
     {u'TOT_PTS_Misc': u'Smugala, Ryan', u'Total_Points': 66.0},
     {u'TOT_PTS_Misc': u'Harmon, Gary', u'Total_Points': 66.0},
     {u'TOT_PTS_Misc': u'Blasinsky, Scott', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Carter III, Laymon', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Coleman, Johnathan', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Venditti, Nick', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Blackwell, Devon', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Kovach, Alex', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Bolden, Antonio', u'Total_Points': 60.0},
     {u'TOT_PTS_Misc': u'Smith, Ryan', u'Total_Points': 60.0}]

a = multikeysort(b, ['-Total_Points', 'TOT_PTS_Misc'])
for item in a:
    print item

ลองใช้คอลัมน์ใดคอลัมน์หนึ่ง คุณจะเห็นการเรียงลำดับย้อนกลับ

ถัดไป: เปลี่ยนเพื่อไม่ใช้คลาสพิเศษ ....


2016-01-17

รับแรงบันดาลใจจากคำตอบนี้วิธีที่ดีที่สุดในการรับสินค้าชิ้นแรกจากเงื่อนไขที่สามารถทำซ้ำได้คืออะไร? , ฉันย่อรหัส:

from operator import itemgetter as i

def multikeysort(items, columns):
    comparers = [
        ((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1))
        for col in columns
    ]
    def comparer(left, right):
        comparer_iter = (
            cmp(fn(left), fn(right)) * mult
            for fn, mult in comparers
        )
        return next((result for result in comparer_iter if result), 0)
    return sorted(items, cmp=comparer)

ในกรณีที่คุณชอบรหัสของคุณ terse


ต่อมา 2016-01-17

สิ่งนี้ใช้ได้กับ python3 (ซึ่งตัดcmpอาร์กิวเมนต์ไปsort):

from operator import itemgetter as i
from functools import cmp_to_key

def cmp(x, y):
    """
    Replacement for built-in function cmp that was removed in Python 3

    Compare the two objects x and y and return an integer according to
    the outcome. The return value is negative if x < y, zero if x == y
    and strictly positive if x > y.

    https://portingguide.readthedocs.io/en/latest/comparisons.html#the-cmp-function
    """

    return (x > y) - (x < y)

def multikeysort(items, columns):
    comparers = [
        ((i(col[1:].strip()), -1) if col.startswith('-') else (i(col.strip()), 1))
        for col in columns
    ]
    def comparer(left, right):
        comparer_iter = (
            cmp(fn(left), fn(right)) * mult
            for fn, mult in comparers
        )
        return next((result for result in comparer_iter if result), 0)
    return sorted(items, key=cmp_to_key(comparer))

แรงบันดาลใจจากคำตอบนี้ฉันจะจัดเรียงแบบกำหนดเองใน Python 3 ได้อย่างไร


วิธีนี้ได้ผลดีที่สุดเพราะฉันสามารถใช้การย้อนกลับกับคีย์หรือคอลัมน์ใดก็ได้ ขอบคุณ!
simi

สิ่งนี้ได้ผลดี ฉันเรียกฟังก์ชันของฉันด้วยรายการและสตริงเป็นพารามิเตอร์ ฉันแยกสตริงก่อนจากนั้นเรียก multikeysort ด้วยรายการและรายการคีย์จากสตริงแยก ไม่สำคัญว่ารายการใดในสตริงจะมีเครื่องหมาย "-" ที่จุดเริ่มต้นของชื่อคอลัมน์เนื่องจากจะใช้ได้กับรายการใดรายการหนึ่งหรือรายการทั้งหมด น่ากลัว ขอบคุณ.
simi

2
ขอบคุณคุณบันทึกวันของฉัน!
Sander van Leeuwen

4
cmp()ไม่พร้อมใช้งานสำหรับ Python3 ดังนั้นฉันต้องกำหนดเองดังที่กล่าวไว้ที่นี่: stackoverflow.com/a/22490617/398514
เสีย

8
@hughdbrown: คุณลบcmpคีย์เวิร์ดออกแต่cmp()ฟังก์ชันยังคงใช้ 4 บรรทัดด้านบน ฉันลองใช้ 3.2, 3.3, 3.4 และ 3.5 พวกเขาทั้งหมดล้มเหลวในการเรียกใช้ฟังก์ชันเนื่องจากcmp()ไม่ได้กำหนดไว้ สัญลักษณ์แสดงหัวข้อย่อยที่สามที่นี่ ( docs.python.org/3.0/whatsnew/3.0.html#ordering-comparisons ) กล่าวถึงการปฏิบัติcmp()ตามที่หายไป
เสีย

56

บทความนี้มีบทสรุปที่ดีเกี่ยวกับเทคนิคต่างๆในการทำเช่นนี้ หากความต้องการของคุณง่ายกว่า "มัลติไบค์สองทิศทางเต็มรูปแบบ" ลองดู เป็นที่ชัดเจนว่าคำตอบที่ยอมรับและโพสต์บล็อกที่ฉันอ้างถึงมีอิทธิพลต่อกันและกันในทางใดทางหนึ่งแม้ว่าฉันจะไม่รู้ว่าลำดับใด

ในกรณีที่ลิงก์เสียนี่เป็นตัวอย่างย่อที่รวดเร็วมากที่ไม่ได้กล่าวถึงข้างต้น:

mylist = sorted(mylist, key=itemgetter('name', 'age'))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), k['age']))
mylist = sorted(mylist, key=lambda k: (k['name'].lower(), -k['age']))

ใกล้เคียงที่สุดที่ฉันสามารถบอกได้ stygianvision ใช้รหัสของฉันและไม่ให้เครดิต Google สำหรับresult = cmp(fn(left), fn(right))
hughdbrown

4
ขอบคุณสำหรับเรื่องย่อตอนนี้ลิงค์ตายไปแล้วจริงๆ :)
Amyth

49

ฉันรู้ว่านี่เป็นคำถามที่ค่อนข้างเก่า แต่ไม่มีคำตอบใดที่พูดถึงว่า Python รับประกันลำดับการจัดเรียงที่มั่นคงสำหรับกิจวัตรการเรียงลำดับเช่นlist.sort()และsorted()ซึ่งหมายความว่ารายการที่เปรียบเทียบเท่ากันจะคงลำดับเดิมไว้

ซึ่งหมายความว่าการเทียบเท่าORDER BY name ASC, age DESC(โดยใช้สัญกรณ์ SQL) สำหรับรายการพจนานุกรมสามารถทำได้ดังนี้:

items.sort(key=operator.itemgetter('age'), reverse=True)
items.sort(key=operator.itemgetter('name'))

สังเกตว่ารายการจะเรียงลำดับอย่างไรก่อนโดยแอตทริบิวต์ "lesser" age(จากมากไปหาน้อย) ตามด้วยแอตทริบิวต์ "major" nameนำไปสู่ลำดับสุดท้ายที่ถูกต้อง

การย้อนกลับ / การย้อนกลับใช้ได้กับทุกประเภทที่สั่งซื้อได้ไม่ใช่แค่ตัวเลขที่คุณสามารถลบล้างได้โดยใส่เครื่องหมายลบไว้ข้างหน้า

และเนื่องจากอัลกอริทึม Timsort ที่ใช้ใน CPython (อย่างน้อย) สิ่งนี้ค่อนข้างเร็วในทางปฏิบัติ


2
ดีมาก. สำหรับชุดข้อมูลระดับปานกลางที่การจัดเรียงชุดหลายครั้งไม่สำคัญนี่สุดยอดมาก! ตามที่คุณชี้ให้เห็นคุณต้องย้อนกลับการเรียงลำดับ python เทียบกับการเรียงลำดับ sql ขอบคุณ.
Greg

การเรียงลำดับที่สองจะทำลายผลลัพธ์ของครั้งแรก ตลกดีที่ไม่มีผู้โหวตคนใดสังเกตเห็น
ภูเขาไฟ

9
ตลกดีที่คุณไม่ได้สังเกตว่าเกณฑ์การเรียงลำดับหลักเป็นไปตามที่แสดงในตัวอย่างของฉันและมีการกล่าวถึงอย่างชัดเจนในความคิดเห็นอื่นเพื่อให้ชัดเจนในกรณีที่คุณไม่สังเกตเห็น
wouter bolsterlee

24
def sortkeypicker(keynames):
    negate = set()
    for i, k in enumerate(keynames):
        if k[:1] == '-':
            keynames[i] = k[1:]
            negate.add(k[1:])
    def getit(adict):
       composite = [adict[k] for k in keynames]
       for i, (k, v) in enumerate(zip(keynames, composite)):
           if k in negate:
               composite[i] = -v
       return composite
    return getit

a = sorted(b, key=sortkeypicker(['-Total_Points', 'TOT_PTS_Misc']))

ว้าว! ที่น่ากลัว มันใช้งานได้ดี ฉันเป็นมือใหม่มากจนฉันรู้สึกว่าจะไม่มีวันรู้เรื่องทั้งหมดนี้ นั่นก็เร็วเช่นกัน ขอบคุณมาก.
simi

แต่จะเกิดอะไรขึ้นถ้าคีย์ที่ส่งไปยัง sortkeypicker เป็นสตริงเช่นนั้น '-Total_Points, TOT_PTS_Misc'?
simi

1
จากนั้นคุณสามารถแยกสตริงออกเป็นอาร์เรย์ก่อนโดยโทรsome_string.split(",")
Jason Creighton

ขอบคุณ. ฉันรู้ว่าฉันสามารถแยกสตริงได้หลังจากที่ฉันแสดงความคิดเห็นแล้ว DOH!
simi

2
แต่ถ้าคุณลบล้างค่าสตริงแทนค่าตัวเลขล่ะ? ฉันไม่คิดว่าจะได้ผล
Nick Perkins

5

ฉันใช้สิ่งต่อไปนี้สำหรับการจัดเรียงอาร์เรย์ 2d ในคอลัมน์จำนวนมาก

def k(a,b):
    def _k(item):
        return (item[a],item[b])
    return _k

สิ่งนี้สามารถขยายให้ใช้งานได้ตามจำนวนรายการโดยพลการ ฉันมักจะคิดว่าการค้นหารูปแบบการเข้าถึงคีย์แบบเรียงลำดับได้ดีกว่าการเขียนตัวเปรียบเทียบแบบแฟนซี

>>> data = [[0,1,2,3,4],[0,2,3,4,5],[1,0,2,3,4]]
>>> sorted(data, key=k(0,1))
[[0, 1, 2, 3, 4], [0, 2, 3, 4, 5], [1, 0, 2, 3, 4]]
>>> sorted(data, key=k(1,0))
[[1, 0, 2, 3, 4], [0, 1, 2, 3, 4], [0, 2, 3, 4, 5]]
>>> sorted(a, key=k(2,0))
[[0, 1, 2, 3, 4], [1, 0, 2, 3, 4], [0, 2, 3, 4, 5]]

4

วันนี้ฉันมีปัญหาคล้าย ๆ กัน - ฉันต้องจัดเรียงรายการในพจนานุกรมโดยลดค่าตัวเลขและค่าสตริงจากน้อยไปมาก เพื่อแก้ปัญหาทิศทางที่ขัดแย้งกันฉันลบล้างค่าจำนวนเต็ม

นี่คือโซลูชันของฉันที่แตกต่างกัน - ตามความเหมาะสม

sorted(b, key=lambda e: (-e['Total_Points'], e['TOT_PTS_Misc']))

ง่ายมาก - และใช้งานได้อย่างมีเสน่ห์

[{'TOT_PTS_Misc': 'Chappell, Justin', 'Total_Points': 96.0},
 {'TOT_PTS_Misc': 'Russo, Brandon', 'Total_Points': 96.0},
 {'TOT_PTS_Misc': 'Utley, Alex', 'Total_Points': 96.0},
 {'TOT_PTS_Misc': 'Foster, Toney', 'Total_Points': 80.0},
 {'TOT_PTS_Misc': 'Lawson, Roman', 'Total_Points': 80.0},
 {'TOT_PTS_Misc': 'Lempke, Sam', 'Total_Points': 80.0},
 {'TOT_PTS_Misc': 'Gnezda, Alex', 'Total_Points': 78.0},
 {'TOT_PTS_Misc': 'Kirks, Damien', 'Total_Points': 78.0},
 {'TOT_PTS_Misc': 'Korecz, Mike', 'Total_Points': 78.0},
 {'TOT_PTS_Misc': 'Worden, Tom', 'Total_Points': 78.0},
 {'TOT_PTS_Misc': 'Burgess, Randy', 'Total_Points': 66.0},
 {'TOT_PTS_Misc': 'Harmon, Gary', 'Total_Points': 66.0},
 {'TOT_PTS_Misc': 'Smugala, Ryan', 'Total_Points': 66.0},
 {'TOT_PTS_Misc': 'Swartz, Brian', 'Total_Points': 66.0},
 {'TOT_PTS_Misc': 'Blackwell, Devon', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Blasinsky, Scott', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Bolden, Antonio', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Carter III, Laymon', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Coleman, Johnathan', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Kovach, Alex', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Smith, Ryan', 'Total_Points': 60.0},
 {'TOT_PTS_Misc': 'Venditti, Nick', 'Total_Points': 60.0}]

0
from operator import itemgetter
from functools import partial

def _neg_itemgetter(key, d):
    return -d[key]

def key_getter(key_expr):
    keys = key_expr.split(",")
    getters = []
    for k in keys:
        k = k.strip()
        if k.startswith("-"):
           getters.append(partial(_neg_itemgetter, k[1:]))
        else:
           getters.append(itemgetter(k))

    def keyfunc(dct):
        return [kg(dct) for kg in getters]

    return keyfunc

def multikeysort(dict_list, sortkeys):
    return sorted(dict_list, key = key_getter(sortkeys)

สาธิต:

>>> multikeysort([{u'TOT_PTS_Misc': u'Utley, Alex', u'Total_Points': 60.0},
                 {u'TOT_PTS_Misc': u'Russo, Brandon', u'Total_Points': 96.0}, 
                 {u'TOT_PTS_Misc': u'Chappell, Justin', u'Total_Points': 96.0}],
                "-Total_Points,TOT_PTS_Misc")
[{u'Total_Points': 96.0, u'TOT_PTS_Misc': u'Chappell, Justin'}, 
 {u'Total_Points': 96.0, u'TOT_PTS_Misc': u'Russo, Brandon'}, 
 {u'Total_Points': 60.0, u'TOT_PTS_Misc': u'Utley, Alex'}]

การแยกวิเคราะห์ค่อนข้างบอบบาง แต่อย่างน้อยก็ช่วยให้มีช่องว่างระหว่างคีย์ได้


แต่เมื่อฉันมีรายการที่สองในสตริงที่มี '-' มันทำให้ฉันมีประเภทตัวดำเนินการที่ไม่ดีสำหรับข้อผิดพลาด unary
simi

คุณไม่สามารถหาค่าลบของสตริงได้
Torsten Marek

ใช่ฉันรู้ แต่นี่เป็นวิธีส่งผ่านพารามิเตอร์แม้ว่าฉันจะทำการแยกค่าหนึ่งหรืออย่างอื่นจะเริ่มต้นด้วย "-" ฉันคิดว่าต้องแยก sortkeys ก่อนที่จะเรียก key_getter ดังนั้นแต่ละรายการในรายการคีย์จะตรวจสอบอักขระตัวแรก ฉันมาถูกทางหรือเปล่า
simi

0

เนื่องจากคุณคุ้นเคยกับแลมด้าอยู่แล้วนี่เป็นวิธีแก้ปัญหาที่ละเอียดน้อยกว่า

>>> def itemgetter(*names):
    return lambda mapping: tuple(-mapping[name[1:]] if name.startswith('-') else mapping[name] for name in names)

>>> itemgetter('a', '-b')({'a': 1, 'b': 2})
(1, -2)

สิ่งนี้ใช้ไม่ได้ ฉันมี: values ​​= ['-Total_Points', 'TOT_PTS_Misc'] แล้ว b เป็นรายการของคำสั่งเมื่อฉันเรียก g = itemgetter (ค่า) (b) ฉันได้รับ AttributeError: วัตถุ 'list' ไม่มีแอตทริบิวต์ 'startwith'
simi

ต้องใช้จำนวนชื่อที่แปรผันไม่ใช่รายชื่อ เรียกแบบนี้ว่า itemgetter (* values) ดูตัวดำเนินการ builtin ที่คล้ายกันสำหรับตัวอย่างอื่น
A. Coady
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.