J'ai un tableau d' Contact
objets:
var contacts:[Contact] = [Contact]()
Classe de contact:
Class Contact:NSOBject {
var firstName:String!
var lastName:String!
}
Et je voudrais trier ce tableau par lastName
et puis par firstName
au cas où certains contacts la même chose lastName
.
Je peux trier selon l'un de ces critères, mais pas les deux.
contacts.sortInPlace({$0.lastName < $1.lastName})
Comment pourrais-je ajouter plus de critères pour trier ce tableau?
Contact
ne devrait probablement pas hériter deNSObject
, 2)Contact
devrait probablement être une structure, et 3)firstName
etlastName
ne devrait probablement pas être des options implicitement déballées.Réponses:
Pensez à ce que signifie «tri selon plusieurs critères». Cela signifie que deux objets sont d'abord comparés par un critère. Ensuite, si ces critères sont les mêmes, les liens seront rompus par les critères suivants, et ainsi de suite jusqu'à ce que vous obteniez l'ordre souhaité.
Ce que vous voyez ici est la
Sequence.sorted(by:)
méthode , qui consulte la fermeture fournie pour déterminer comment les éléments se comparent.Si votre tri est utilisé dans de nombreux endroits, il peut être préférable de rendre votre type conforme au
Comparable
protocole . De cette façon, vous pouvez utiliserSequence.sorted()
method , qui consulte votre implémentation de l'Comparable.<(_:_:)
opérateur pour déterminer comment les éléments se comparent. De cette façon, vous pouvez trier n'importe lequelSequence
desContact
s sans jamais avoir à dupliquer le code de tri.la source
else
corps doit être entre,{ ... }
sinon le code ne se compile pas.sort
vssortInPlace
voir ici . Voir aussi ceci ci - dessous, c'est beaucoup plus modulairesortInPlace
n'est plus disponible dans Swift 3, vous devez plutôt l'utilisersort()
.sort()
fera muter le tableau lui-même. Il existe également une nouvelle fonction nomméesorted()
qui retournera un tableau trié==
n'est pas une bonne idée. Cela ne fonctionne que pour 2 propriétés. Pas plus que cela, et vous commencez à vous répéter avec beaucoup d'expressions booléennes composéesUtilisation de tuples pour faire une comparaison de plusieurs critères
Un moyen très simple d'effectuer un tri selon plusieurs critères (c'est-à-dire le tri par une comparaison, et si équivalent, puis par une autre comparaison) est d'utiliser des tuples , car les opérateurs
<
et>
ont des surcharges pour eux qui effectuent des comparaisons lexicographiques.Par exemple:
Cela comparera d'
lastName
abord les propriétés des éléments . S'ils ne sont pas égaux, l'ordre de tri sera basé sur une<
comparaison avec eux. S'ils sont égaux, alors il passera à la prochaine paire d'éléments dans le tuple, c'est-à-dire en comparant lesfirstName
propriétés.La bibliothèque standard fournit
<
et>
surcharge les tuples de 2 à 6 éléments.Si vous voulez différents ordres de tri pour différentes propriétés, vous pouvez simplement permuter les éléments dans les tuples:
Cela va maintenant trier par
lastName
décroissant, puisfirstName
croissant.Définir un
sort(by:)
surcharge qui prend plusieurs prédicatsInspiré par la discussion sur le tri des collections avec
map
fermetures et SortDescriptors , une autre option serait de définir une surcharge personnalisée desort(by:)
etsorted(by:)
qui traite de plusieurs prédicats - où chaque prédicat est considéré à son tour pour décider de l'ordre des éléments.(Le
secondPredicate:
paramètre est malheureux, mais est nécessaire pour éviter de créer des ambiguïtés avec lasort(by:)
surcharge existante )Cela nous permet alors de dire (en utilisant le
contacts
tableau de plus tôt):Bien que le site d'appel ne soit pas aussi concis que la variante tuple, vous gagnez en clarté supplémentaire avec ce qui est comparé et dans quel ordre.
Se conformer à
Comparable
Si vous comptez faire ce genre de comparaisons régulièrement, comme le suggèrent @AMomchilov & @appzYourLife , vous pouvez vous conformer
Contact
àComparable
:Et maintenant, appelez simplement
sort()
pour un ordre croissant:ou
sort(by: >)
pour un ordre décroissant:Définition d'ordres de tri personnalisés dans un type imbriqué
Si vous souhaitez utiliser d'autres ordres de tri, vous pouvez les définir dans un type imbriqué:
puis appelez simplement comme:
la source
contacts.sort { ($0.lastName, $0.firstName) < ($1.lastName, $1.firstName) }
Aidé. Mercicontacts.sort { ($0.lastName ?? "", $0.firstName ?? "") < ($1.lastName ?? "", $1.firstName ?? "") }
.""
comparaison avec d'autres chaînes (il vient avant les chaînes non vides). C'est un peu implicite, un peu magique et inflexible si vous voulez que lesnil
s viennent plutôt à la fin de la liste. Je vous recommande de jeter un œil à manilComparator
fonction stackoverflow.com/a/44808567/3141234Une autre approche simple pour le tri avec 2 critères est présentée ci-dessous.
Vérifiez le premier champ, dans ce cas c'est le cas
lastName
, s'ils ne sont pas égaux, triez parlastName
, silastName
les valeurs sont égales, puis triez par le deuxième champ, dans ce casfirstName
.la source
La seule chose que les tris lexicographiques ne peuvent pas faire comme décrit par @Hamish est de gérer différentes directions de tri, disons le tri par le premier champ décroissant, le champ suivant croissant, etc.
J'ai créé un article de blog sur la façon de procéder dans Swift 3 et de garder le code simple et lisible.
Vous pouvez le trouver ici:
http://master-method.com/index.php/2016/11/23/sort-a-sequence-ie-arrays-of-objects-by-multiple-properties-in-swift-3/Vous pouvez également trouver un référentiel GitHub avec le code ici:
https://github.com/jallauca/SortByMultipleFieldsSwift.playground
L'essentiel de tout cela, par exemple, si vous avez une liste d'emplacements, vous pourrez le faire:
la source
Cette question a déjà de nombreuses réponses intéressantes, mais je veux pointer vers un article - Sort Descriptors in Swift . Nous avons plusieurs façons de faire le tri selon plusieurs critères.
En utilisant NSSortDescriptor, cette façon a quelques limitations, l'objet doit être une classe et hérite de NSObject.
Ici, par exemple, nous voulons trier par nom, puis prénom, enfin par année de naissance. Et nous voulons le faire sans tenir compte de la casse et en utilisant les paramètres régionaux de l'utilisateur.
Utilisation de la méthode Swift pour trier par nom / prénom. Cette méthode devrait fonctionner avec les deux class / struct. Cependant, nous ne trions pas par yearOfBirth ici.
Manière rapide d'inmiter NSSortDescriptor. Cela utilise le concept que «les fonctions sont un type de première classe». SortDescriptor est un type de fonction, prend deux valeurs, renvoie un booléen. Dites sortByFirstName, nous prenons deux paramètres ($ 0, $ 1) et comparons leurs prénoms. Les fonctions de combinaison prennent un tas de SortDescriptors, les comparent tous et donnent des ordres.
C'est bien car vous pouvez l'utiliser à la fois avec struct et class, vous pouvez même l'étendre pour le comparer avec nils.
Pourtant, la lecture de l' article original est fortement suggérée. Il a beaucoup plus de détails et bien expliqué.
la source
Je recommanderais d'utiliser la solution tuple de Hamish car elle ne nécessite pas de code supplémentaire.
Si vous voulez quelque chose qui se comporte comme des
if
instructions mais simplifie la logique de branchement, vous pouvez utiliser cette solution, qui vous permet d'effectuer les opérations suivantes:Voici les fonctions qui vous permettent de faire cela:
Si vous souhaitez le tester, vous pouvez utiliser ce code supplémentaire:
La principale différence par rapport à la solution de Jamie est que l'accès aux propriétés est défini en ligne plutôt que comme des méthodes statiques / d'instance sur la classe. Par exemple
$0.family
au lieu deAnimal.familyCompare
. Et ascendant / descendant est contrôlé par un paramètre au lieu d'un opérateur surchargé. La solution de Jamie ajoute une extension sur Array alors que ma solution utilise la méthode intégréesort
/sorted
mais nécessite la définition de deux autres:compare
etcomparisons
.Par souci d'exhaustivité, voici comment ma solution se compare à la solution tuple de Hamish . Pour démontrer, je vais utiliser un exemple sauvage où nous voulons trier les gens par
(name, address, profileViews)
la solution de Hamish évaluera chacune des 6 valeurs de propriété exactement une fois avant le début de la comparaison. Cela peut ne pas être souhaité ou non. Par exemple, en supposant qu'ilprofileViews
s'agit d'un appel réseau coûteux, nous pouvons éviter d'appelerprofileViews
moins que ce ne soit absolument nécessaire. Ma solution évitera d'évaluerprofileViews
jusqu'à$0.name == $1.name
et$0.address == $1.address
. Cependant, lorsqu'il sera évalué,profileViews
il évaluera probablement plusieurs fois une fois.la source
Que diriez-vous:
la source
lexicographicallyPrecedes
exige que tous les types du tableau soient identiques. Par exemple[String, String]
. Ce que OP veut probablement, c'est mélanger et assortir les types:[String, Int, Bool]
ainsi ils pourraient le faire[$0.first, $0.age, $0.isActive]
.cela a fonctionné pour mon tableau [String] dans Swift 3 et il semble que dans Swift 4 est ok
la source