Eu tenho uma série de Contact
objetos:
var contacts:[Contact] = [Contact]()
Classe de contato:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
E eu gostaria de classificar essa matriz por lastName
e então por firstName
caso alguns contatos tenham o mesmo lastName
.
Posso classificar por um desses critérios, mas não ambos.
contacts.sortInPlace({$0.lastName < $1.lastName})
Como posso adicionar mais critérios para classificar essa matriz?
Contact
provavelmente não deve herdar deNSObject
, 2)Contact
provavelmente deve ser uma estrutura e 3)firstName
elastName
provavelmente não deve ser opcional implicitamente desembrulhado.Respostas:
Pense no que significa "classificação por vários critérios". Isso significa que dois objetos são comparados primeiro por um critério. Então, se esses critérios forem iguais, os empates serão desfeitos pelo próximo critério e assim por diante até obter a ordem desejada.
O que você está vendo aqui é o
Sequence.sorted(by:)
método , que consulta o fechamento fornecido para determinar como os elementos se comparam.Se a sua classificação for usada em muitos lugares, pode ser melhor ajustar o seu tipo ao
Comparable
protocolo . Dessa forma, você pode usar oSequence.sorted()
método , que consulta sua implementação doComparable.<(_:_:)
operador para determinar como os elementos se comparam. Dessa forma, você pode classificar qualquer umSequence
dosContact
s sem nunca ter que duplicar o código de classificação.fonte
else
corpo deve estar entre,{ ... }
caso contrário, o código não compila.sort
vs.sortInPlace
veja aqui . Veja isso abaixo, é muito mais modularsortInPlace
NÃO está mais disponível no Swift 3, em vez dele você tem que usarsort()
.sort()
irá transformar o próprio array. Além disso, há uma nova função chamadasorted()
que retornará uma matriz classificada==
não é uma boa ideia. Funciona apenas para 2 propriedades. Mais do que isso, e você começa a se repetir com um monte de expressões booleanas compostasUsando tuplas para fazer uma comparação de vários critérios
Uma maneira realmente simples de realizar uma classificação por vários critérios (ou seja, classificar por uma comparação e, se equivalente, por outra comparação) é usando tuplas , já que os operadores
<
e>
têm sobrecargas para eles que realizam comparações lexicográficas.Por exemplo:
Isso irá comparar as
lastName
propriedades dos elementos primeiro. Se eles não forem iguais, a ordem de classificação será baseada em uma<
comparação com eles. Se eles forem iguais, ele se moverá para o próximo par de elementos na tupla, ou seja, comparando asfirstName
propriedades.A biblioteca padrão fornece
<
e>
sobrecarrega tuplas de 2 a 6 elementos.Se você quiser diferentes ordens de classificação para diferentes propriedades, pode simplesmente trocar os elementos nas tuplas:
Agora, isso será classificado por ordem
lastName
decrescente e depoisfirstName
crescente.Definindo uma
sort(by:)
sobrecarga que leva vários predicadosInspirado na discussão sobre Sorting Collections with
map
closures e SortDescriptors , outra opção seria definir uma sobrecarga customizada desort(by:)
esorted(by:)
que lida com vários predicados - onde cada predicado é considerado por sua vez para decidir a ordem dos elementos.(O
secondPredicate:
parâmetro é lamentável, mas é necessário para evitar a criação de ambigüidades com asort(by:)
sobrecarga existente )Isso então nos permite dizer (usando a
contacts
matriz anterior):Embora o call-site não seja tão conciso quanto a variante de tupla, você ganha clareza adicional com o que está sendo comparado e em que ordem.
De acordo com
Comparable
Se você vai fazer esse tipo de comparação regularmente, como @AMomchilov e @appzYourLife sugerem, você pode se conformar
Contact
aComparable
:E agora é só pedir
sort()
uma ordem crescente:ou
sort(by: >)
para uma ordem decrescente:Definição de ordens de classificação personalizadas em um tipo aninhado
Se você tiver outras ordens de classificação que deseja usar, poderá defini-las em um tipo aninhado:
e simplesmente chame como:
fonte
contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) }
Ajudou. Obrigadocontacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }
.""
compara a outras strings (vem antes das strings não vazias). É meio implícito, meio mágico e inflexível se você quiser que osnil
s apareçam no final da lista. Eu recomendo que você dê uma olhada na minhanilComparator
função stackoverflow.com/a/44808567/3141234Outra abordagem simples para classificação com 2 critérios é mostrada abaixo.
Verifique o primeiro campo, neste caso é
lastName
, se eles não são iguais classifique porlastName
, selastName
's são iguais, então classifique pelo segundo campo, neste casofirstName
.fonte
A única coisa que as classificações lexicográficas não podem fazer conforme descrito por @Hamish é lidar com diferentes direções de classificação, digamos classificar pelo primeiro campo descendente, o próximo campo ascendente, etc.
Criei uma postagem no blog sobre como fazer isso no Swift 3 e manter o código simples e legível.
Você pode encontrá-lo aqui:
http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/Você também pode encontrar um repositório GitHub com o código aqui:
https://github.com/jallauca/SortByMultipleFieldsSwift.playground
A essência de tudo, digamos, se você tiver uma lista de locais, será capaz de fazer isso:
fonte
Essa pergunta já tem muitas respostas excelentes, mas quero apontar para um artigo - Classificar Descritores em Swift . Temos várias maneiras de fazer a classificação de vários critérios.
Usando NSSortDescriptor, desta forma tem algumas limitações, o objeto deve ser uma classe e herda de NSObject.
Aqui, por exemplo, queremos classificar por sobrenome, depois nome e finalmente por ano de nascimento. E queremos fazer isso de forma insensível e usando a localidade do usuário.
Usando o método Swift de classificação com sobrenome / nome. Dessa forma, deve funcionar com classe / estrutura. No entanto, não classificamos por yearOfBirth aqui.
Maneira rápida de iniciar NSSortDescriptor. Isso usa o conceito de que 'funções são um tipo de primeira classe'. SortDescriptor é um tipo de função, recebe dois valores e retorna um bool. Diga sortByFirstName, pegamos dois parâmetros ($ 0, $ 1) e comparamos seus primeiros nomes. As funções de combinação pegam vários SortDescriptors, comparam todos eles e dão ordens.
Isso é bom porque você pode usá-lo com struct e classe, você pode até mesmo estendê-lo para comparar com nils.
Ainda assim, a leitura do artigo original é altamente recomendável. Tem muito mais detalhes e bem explicados.
fonte
Eu recomendaria usar a solução de tupla de Hamish, pois não requer código extra.
Se você quiser algo que se comporte como
if
instruções, mas simplifique a lógica de ramificação, pode usar esta solução, que permite fazer o seguinte:Aqui estão as funções que permitem que você faça isso:
Se quiser testá-lo, você pode usar este código extra:
As principais diferenças da solução de Jamie é que o acesso às propriedades são definidos inline, em vez de métodos estáticos / de instância na classe. Por exemplo
$0.family
vez deAnimal.familyCompare
. E a ascensão / descida é controlada por um parâmetro em vez de um operador sobrecarregado. A solução de Jamie adiciona uma extensão em Array, enquanto minha solução usa o métodosort
/ embutidosorted
, mas requer dois adicionais a serem definidos:compare
ecomparisons
.Para completar, veja como minha solução se compara à solução de tupla de Hamish . Para demonstrar, usarei um exemplo selvagem em que queremos classificar as pessoas pela
(name, address, profileViews)
solução de Hamish para avaliar cada um dos 6 valores de propriedade exatamente uma vez antes do início da comparação. Isso pode não ser ou não desejado. Por exemplo, supondo queprofileViews
seja uma chamada de rede cara, podemos evitar fazer chamadas, aprofileViews
menos que seja absolutamente necessário. Minha solução evitará avaliarprofileViews
até$0.name == $1.name
e$0.address == $1.address
. No entanto, quando avaliaprofileViews
, provavelmente irá avaliar muito mais vezes de uma vez.fonte
E se:
fonte
lexicographicallyPrecedes
requer que todos os tipos na matriz sejam iguais. Por exemplo[String, String]
. O que o OP provavelmente deseja é misturar e combinar tipos:[String, Int, Bool]
para que eles possam fazer[$0.first, $0.age, $0.isActive]
.que funcionou para meu array [String] no Swift 3 e parece que no Swift 4 está ok
fonte