पायथन में कई कुंजियों द्वारा वस्तुओं को कैसे छाँटा जाए?


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

लैंबडा लाइन का उपयोग कैसे किया जा सकता है जो सूची को सॉर्ट करेगा, बहुस्तरीय फ़ंक्शन में पास होने वाली कुंजियों की एक मनमानी संख्या के लिए, और यह ध्यान रखें कि सॉर्टकी में किसी भी संख्या की कुंजी हो सकती है और जिन्हें उलट सॉर्ट की आवश्यकता है इसके पहले '-' के साथ?

जवाबों:


72

यह उत्तर शब्दकोश में किसी भी प्रकार के कॉलम के लिए काम करता है - नकारात्मक कॉलम को संख्या की आवश्यकता नहीं है।

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)

मामले में आप अपने कोड की तरह है।


बाद में 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))

इस उत्तर से प्रेरित होकर मुझे पायथन 3 में कस्टम प्रकार कैसे करना चाहिए?


यह सबसे अच्छा काम करता है क्योंकि मैं किसी भी कुंजी या कॉलम पर रिवर्स का उपयोग कर सकता हूं। धन्यवाद!
— simi

तो यह अच्छी तरह से काम करता है। मैं अपने फ़ंक्शन को सूची और स्ट्रिंग के साथ पैरामीटर के रूप में कहता हूं। मैं पहले स्ट्रिंग को विभाजित करता हूं फिर मल्टीकेइज़ॉर्ट को सूची और कुंजी की सूची को विभाजित स्ट्रिंग से बुलाता हूं। इससे कोई फर्क नहीं पड़ता कि स्ट्रिंग के नाम की शुरुआत में स्ट्रिंग में किस आइटम में '-' है, क्योंकि यह आइटम या सभी वस्तुओं के साथ काम करेगा। बहुत बढ़िया। धन्यवाद।
— सिमि जूल

2
धन्यवाद, आपने मेरा दिन बचा लिया!
— सेंडर वैन लीउवेन

4
cmp()Python3 के लिए उपलब्ध नहीं है, इसलिए मुझे इसे स्वयं परिभाषित करना था, जैसा कि यहाँ बताया गया है: stackoverflow.com/a/22490617/398514
— 19

8
@hughdbrown: आपने cmpकीवर्ड हटा दिया है , लेकिन cmp()फ़ंक्शन अभी भी 4 लाइनों के ऊपर उपयोग किया जाता है। मैंने इसे 3.2, 3.3, 3.4 और 3.5 के साथ आज़माया, वे सभी फ़ंक्शन कॉल में विफल रहे, क्योंकि cmp()परिभाषित नहीं है। यहां तीसरी गोली ( docs.python.org/3.0/whatsnew/3.0.html#ordering-comparison ) के अनुसार इलाज का उल्लेख है cmp()।
— 16

55

इस लेख में ऐसा करने के लिए विभिन्न तकनीकों पर एक अच्छा तरीका है। यदि आपकी आवश्यकताएं "पूर्ण द्विदिश बहुस्तरीय" की तुलना में सरल हैं, तो एक नज़र डालें। यह स्वीकार किए गए उत्तर और ब्लॉग पोस्ट को स्पष्ट करता है मैंने केवल एक दूसरे को किसी तरह से प्रभावित किया है, हालांकि मुझे नहीं पता कि कौन सा आदेश है।

यदि लिंक की मृत्यु हो जाती है, तो ऊपर दिए गए उदाहरणों का एक बहुत ही त्वरित सारांश नहीं है:

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

जैसा कि मैं बता सकता हूं, शैलीकरण मेरे कोड का उपयोग करता है और कोई क्रेडिट नहीं देता है। Google के लिएresult = cmp(fn(left), fn(right))
— hughdbrown

4
सिनॉप्सिस के लिए धन्यवाद, लिंक वास्तव में अब मर चुका है। :)
— एमीथ

49

मुझे पता है कि यह एक पुराना सवाल है, लेकिन कोई भी उत्तर नहीं बताता है कि पायथन अपने सॉर्टिंग रूटीन के लिए एक स्थिर सॉर्ट ऑर्डर की गारंटी देता है जैसे कि , list.sort()और sorted()इसका मतलब है कि आइटम जो समान हैं, उनके मूल क्रम को बनाए रखते हैं।

इसका मतलब है कि ORDER BY name ASC, age DESCशब्दकोशों की सूची के लिए (SQL संकेतन का उपयोग करते हुए) इस तरह किया जा सकता है:

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

ध्यान दें कि आइटम को पहले "कम" विशेषता age(अवरोही) द्वारा कैसे सॉर्ट किया जाता है , फिर "प्रमुख" विशेषता द्वारा name, सही अंतिम क्रम तक ले जाता है।

सभी क्रमबद्ध प्रकारों के लिए उलट / इनवर्टिंग काम करता है, न केवल संख्याएँ जिन्हें आप एक ऋण चिह्न के सामने रख कर नकार सकते हैं।

और (कम से कम) CPython में इस्तेमाल किए जाने वाले Timsort एल्गोरिथम के कारण, यह वास्तव में व्यवहार में तेज है।


2
बहुत अच्छा। मध्यम डेटा सेट के लिए, जहाँ सेट को कई बार छाँटने से कोई फर्क नहीं पड़ता, यह सुपर कूल है! जैसा कि आप बताते हैं, आपको sql सॉर्ट की तुलना में अजगर प्रकार को उल्टा करना होगा। धन्यवाद।
— ग्रेग

दूसरा प्रकार पहले के परिणाम को तोड़ देगा। मज़ेदार है कि किसी भी अपवित्र ने इस पर ध्यान नहीं दिया।
— ज्वालामुखी

9
मज़ेदार यह है कि आपने ध्यान नहीं दिया कि प्राथमिक प्रकार की कसौटी अंतिम है, जैसा कि मेरे उदाहरण में दिखाया गया है, और स्पष्ट रूप से अन्य टिप्पणी में उल्लेख किया गया है कि आप इसे नोटिस नहीं करते हैं।
— राउटर बोलस्टरली

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

वाह! यह बहुत अच्छा है। यह बहुत अच्छा काम करता है। मैं ऐसा नौसिखिया हूं कि मुझे लगता है कि मैं यह सब जानने के बिंदु पर कभी नहीं पहुंचूंगा। वह भी तेज था। आपका बहुत बहुत धन्यवाद।
— सिमी जूल

लेकिन, क्या होगा अगर सॉर्टकीपर को भेजी गई कुंजी एक स्ट्रिंग है, जैसे कि '-Total_Points, TOT_PTS_Misc'?
— सिमी जूल

1
तब आप स्ट्रिंग को पहले एक सरणी में विभाजित कर सकते थेsome_string.split(",")
— जेसन क्रेइटन

धन्यवाद। मुझे एहसास हुआ कि मैं पहले से टिप्पणी करने के बाद, मैं स्ट्रिंग का विभाजन कर सकता हूं। DOH!
— सिमी जूल

2
लेकिन क्या होगा अगर आप संख्या मूल्य के बजाय स्ट्रिंग मूल्य को नकारते हैं? मुझे नहीं लगता कि यह काम करेगा।
— निक पर्किन्स

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 - error के लिए एक खराब ऑपरेंड प्रकार देता है।
— सिमी

आप एक स्ट्रिंग का नकारात्मक नहीं ले सकते।
— टॉर्स्टन मारेक

हां, मुझे पता है, लेकिन यह है कि मापदंडों को कैसे पारित किया जाता है। यहां तक ​​कि अगर मैं एक विभाजन करता हूं, तो एक या दूसरा '-' के साथ शुरू होगा। मुझे लगता है कि key_getter को कॉल करने से पहले सॉर्टेक को विभाजित करने की आवश्यकता है, इस तरह से कुंजी सूची में प्रत्येक आइटम पहले वर्ण की जांच करेगा। क्या मैं सही रास्ते पर हूं?
— सिमी जूल

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)

यह काम नहीं करता। मेरे पास: मान = ['-Total_Points', 'TOT_PTS_Misc'] तब b के रूप में dicts की सूची जब मैं g = आइटमगेटर (मान) कहता हूं (b) मुझे एट्रीब्यूट मिलता है 'सूची' ऑब्जेक्ट में कोई विशेषता 'startswith' नहीं है
— simi

यह नामों की एक चर संख्या लेता है, नामों की सूची नहीं। इसे इस तरह से कहें: आइटमगेटर (* मान)। एक अन्य उदाहरण के लिए इसी तरह के बिल्टइन ऑपरेटर पर नजर डालें।
— ए। कोदी जूल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.