Como usar uma função de comparação personalizada em Python 3?

98

No Python 2.x , eu poderia passar funções personalizadas para funções ordenadas e classificadas

>>> x=['kar','htar','har','ar']
>>>
>>> sorted(x)
['ar', 'har', 'htar', 'kar']
>>> 
>>> sorted(x,cmp=customsort)
['kar', 'htar', 'har', 'ar']

Porque, na Minha língua, as consoantes vêm com esta ordem

"k","kh",....,"ht",..."h",...,"a"

Mas no Python 3.x , parece que não consegui passar a cmppalavra-chave

>>> sorted(x,cmp=customsort)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'cmp' is an invalid keyword argument for this function

Existe alguma alternativa ou devo escrever minha própria função classificada também?

Nota: simplifiquei usando "k", "kh", etc. Os caracteres reais são Unicodes e ainda mais complicados, às vezes há vogais que vêm antes e depois das consoantes, fiz a função de comparação personalizada, então essa parte está ok. O único problema é que não consegui passar minha função de comparação personalizada para classificado ou .sort

VOCÊS
fonte
você já tentou apenas sorted(x)?
SilentGhost
@SilentGhost, Para ter certeza, acabei de tentar novamente, Claro que não funciona, porque meu idioma original não está na lista de localidades suportada pela Operation Systems para fazer a classificação.
VOCÊ,
1
Você pode envolver seu cmp como uma função-chave. Pesquise no site HowToSorting por cmp_to_key.
Frank
aqui está algo semelhante stackoverflow.com/questions/49327344/…
Eziz Durdyyev

Respostas:

50

Use o keyargumento (e siga a receita sobre como converter sua cmpfunção antiga em uma keyfunção).

functoolstem uma função cmp_to_keymencionada em docs.python.org/3.6/library/functools.html#functools.cmp_to_key

Tim Pietzcker
fonte
+1, parece que a receita me deu uma solução alternativa, mas acho que vou perder um pouco de desempenho passando todos os operadores de comparação < > = para intermediários, já que minha classificação personalizada original é escrita em C, tinha cerca de 1 / 2x a velocidade de classificação padrão.
VOCÊ,
2
(Acabei de olhar seu perfil) Sua empresa está bloqueando o acesso ao Google e StackOverflow? Quão estúpidos eles podem ser? Mas sobre sua resposta: estou interessado na diminuição real do desempenho. Você pode timeit?
Tim Pietzcker,
4
Eu fiz alguns benchmarks, parece cerca de 4x mais lento do que passar a função de comparação C customizada diretamente.
VOCÊ,
2
E se eu precisar de uma função-chave E uma função cmp? Quero classificar uma lista de dicionários por uma chave personalizada em cada dicionário. sorted_rows = sorted(rows, key=itemgetter('name'), cmp=locale.strxfrm)fornece TypeError: 'cmp' é um argumento de palavra-chave inválido para esta função, em Python 3.2 :(
bitek
4
functools tem uma função cmp_to_key na biblioteca padrão: docs.python.org/3.6/library/functools.html
Martín Fixman
59

Use a keypalavra - chave e functools.cmp_to_key para transformar sua função de comparação:

sorted(x, key=functools.cmp_to_key(customsort))
aknuds1
fonte
17

Em vez de Customizar (), você precisa de uma função que traduza cada palavra em algo que Python já sabe como classificar. Por exemplo, você pode traduzir cada palavra em uma lista de números onde cada número representa onde cada letra ocorre em seu alfabeto. Algo assim:

my_alphabet = ['a', 'b', 'c']

def custom_key(word):
   numbers = []
   for letter in word:
      numbers.append(my_alphabet.index(letter))
   return numbers

x=['cbaba', 'ababa', 'bbaa']
x.sort(key=custom_key)

Como seu idioma inclui letras de vários caracteres, sua função custom_key obviamente precisará ser mais complicada. Isso deve lhe dar uma idéia geral.

Daniel Stutzbach
fonte
Obrigado +1, acho que é o jeito da UTI. mas, como meu idioma não tem separadores de palavras e não tem regras padrão de romanização, acho que vai levar algum tempo para pesquisar.
VOCÊ,
9

Um exemplo completo de python3 cmp_to_key lambda:

from functools import cmp_to_key

nums = [28, 50, 17, 12, 121]
nums.sort(key=cmp_to_key(lambda x, y: 1 if str(x)+str(y) < str(y)+str(x) else -1))

compare à classificação de objetos comuns:

class NumStr:
    def __init__(self, v):
        self.v = v
    def __lt__(self, other):
        return self.v + other.v < other.v + self.v


A = [NumStr("12"), NumStr("121")]
A.sort()
print(A[0].v, A[1].v)

A = [obj.v for obj in A]
print(A)
Charlie 木匠
fonte
4

Não sei se isso vai ajudar, mas você pode conferir o localemódulo. Parece que você pode definir o local para o seu idioma e usar locale.strcollpara comparar strings usando as regras de classificação do seu idioma.

Mark Tolonen
fonte
Isso é verdade para linguagens populares, mas minha linguagem não é totalmente suportada por Operation Systems, ICU e unicode.org, então isso está fora de questão, mas +1 para uma boa sugestão.
VOCÊ,
-2

Em keyvez disso, use o argumento. Ele recebe uma função que obtém o valor sendo processado e retorna um único valor fornecendo a chave a ser usada para classificar.

sorted(x, key=somekeyfunc)
Ignacio Vazquez-Abrams
fonte
3
a tecla aceita apenas uma função de parâmetro, cmp tem 2 parâmetros, eles têm comportamento diferente. e acabei de testar, obtive um erro, porque a palavra-chave chave passa apenas um parâmetro,TypeError: customsort() takes exactly 2 positional arguments (1 given)
VOCÊ,