파이썬에서 여러 키로 객체를 정렬하는 방법은 무엇입니까?


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

multikeysort 함수로 전달되는 임의의 수의 키에 대해 목록을 정렬하는 람다 줄을 어떻게 사용할 수 있으며, sortkey에 여러 키가있을 수 있으며 역 정렬이 필요한 키를 식별 할 수 있다는 점을 고려합니다. 앞에 '-'가 있습니까?

답변:


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

이 답변에서 영감을 얻었 습니다 Python 3에서 사용자 지정 정렬을 수행하려면 어떻게해야합니까?


모든 키 또는 열에 반대 방향을 사용할 수 있기 때문에 가장 잘 작동합니다. 감사합니다!
— simi

그래서 이것은 잘 작동합니다. 목록과 문자열을 매개 변수로 사용하여 함수를 호출합니다. 먼저 문자열을 분할 한 다음 분할 된 문자열의 목록과 키 목록을 사용하여 다중 키 정렬을 호출합니다. 열 이름의 시작 부분에 '-'가있는 문자열의 항목은 항목 또는 모든 항목에서 작동하므로 중요하지 않습니다. 대박. 감사합니다.
— simi

2
고마워, 당신은 내 하루를 구했습니다!
— Sander van Leeuwen 2014 년

4
cmp()Python3에서는 사용할 수 없으므로 여기에서 언급했듯이 직접 정의해야했습니다. stackoverflow.com/a/22490617/398514
— pferate

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()사라진 것으로 취급하는 것을 언급합니다 .
— pferate

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

내가 말할 수있는 한, stygianvision은 내 코드를 사용하고 신용을 제공하지 않습니다. Google forresult = cmp(fn(left), fn(right))
— hughdbrown

4
시놉시스 덕분에 Link는 실제로 죽었습니다. :)
— Amyth

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

항목이 먼저 'lesser'속성 age(내림차순) 으로 정렬 된 다음 'major'속성으로 name정렬되어 올바른 최종 순서로 이어지는 방식에 유의하세요 .

반전 / 반전은 마이너스 기호를 앞에 넣어 부정 할 수있는 숫자뿐만 아니라 모든 주문 가능한 유형에 대해 작동합니다.

그리고 (적어도) CPython에서 사용되는 Timsort 알고리즘 때문에 실제로 이것은 실제로 다소 빠릅니다.


2
아주 좋아. 세트를 여러 번 정렬하는 것이 중요하지 않은 중간 데이터 세트의 경우 이것은 매우 멋지다! 지적했듯이 sql 정렬과 비교하여 python 정렬을 반대로해야합니다. 감사.
— 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(",")
— 제이슨 크레이 톤

감사합니다. 나는 이미 주석을 달고 나서 문자열을 나눌 수 있다는 것을 깨달았습니다. 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

오늘 비슷한 문제가있었습니다. 숫자 값을 내림차순으로, 문자열 값을 오름차순으로 사전 항목을 정렬해야했습니다. 충돌하는 방향 문제를 해결하기 위해 정수 값을 부정했습니다.

다음은 내 솔루션의 변형입니다-OP에 적용 가능

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

구문 분석은 약간 취약하지만 적어도 키 사이에 다양한 수의 공백을 허용합니다.


그러나 문자열에 '-'가있는 두 번째 항목이 있으면 단항 오류에 대해 잘못된 피연산자 유형을 제공합니다.
— simi

문자열의 음수를 취할 수 없습니다.
— Torsten Marek

예, 압니다. 그러나 이것이 매개 변수가 전달되는 방법입니다. 분할을해도 하나 또는 다른 하나는 '-'로 시작합니다. key_getter를 호출하기 전에 sortkey를 분할해야한다고 생각합니다. 그러면 키 목록의 각 항목이 첫 번째 문자를 확인합니다. 내가 올바른 길을 가고 있는가?
— 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)

작동하지 않습니다. I는있다 : 값 = '-Total_Points', 'TOT_PTS_Misc'] 다음 dicts의 목록으로 I (b)는 g = itemgetter (값)를 호출 I GET AttributeError B '에서'오브젝트가 속성 'startswith를'없다
— 미

이름 목록이 아닌 가변 개수의 이름을 사용합니다. 다음과 같이 호출하십시오 : itemgetter (* values). 다른 예제를 위해 유사한 내장 operator.itemgetter를 살펴보십시오.
— A. Coady
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.