1+ import java.util.Comparator
2+ import kotlin.reflect.KClass
3+ import kotlin.reflect.KProperty1
4+ import kotlin.reflect.full.memberProperties
5+
6+ object SortingDelegate {
7+
8+ /* *
9+ * Takes two lists and throws an [IllegalArgumentException] if either of the lists are empty or if they are different sizes.
10+ * @param aEmptyMessage message to be printed if [listA] is empty; optionally use "%d" as a template/placeholder for the size of [listA]
11+ * @param bEmptyMessage message to be printed if [listB] is empty; optionally use "%d" as a template/placeholder for the size of [listB]
12+ * @param notSameSizeMessage message to br printed if lists are not the same size; optionally use the first "%d" as a template/placeholder for the size of [listB]
13+ * @return a [Pair] of [listA].size, [listB].size
14+ * */
15+ fun <A , B > requireNotEmptyAndSameSize (
16+ listA : Collection <A >, // ascending
17+ listB : Collection <B >, // sortCriteria
18+ aEmptyMessage : String = "List A is empty.",
19+ bEmptyMessage : String = "List B is empty.",
20+ notSameSizeMessage : String = "Lists are not same size; List A is size %d, List B is size %d."
21+ ): Pair <Int , Int > {
22+ val sizeA = listA.size
23+ val sizeB = listB.size
24+ require(sizeA > 0 ) { System .out .printf(aEmptyMessage, sizeA) }
25+ require(sizeB > 0 ) { System .out .printf(bEmptyMessage, sizeB) }
26+ require(sizeB == sizeA) { System .out .printf(notSameSizeMessage, sizeA, sizeB) }
27+ return Pair (sizeA, sizeB)
28+ }
29+
30+ /* *
31+ * Sorts a list by mutliple criteria
32+ * NOTE: mutates the provided list in the process
33+ * @sample sort(
34+ myList,
35+ listOf(Class::myParameter1.name, Class::myParameter2.name),
36+ listOf(true, false)
37+ )
38+ */
39+ @JvmName(" sortWithListGivenAsParameters" )
40+ inline fun <reified T > sort (
41+ workingList : MutableList <T >,
42+ sortCriteria : List <String >,
43+ ascending : List <Boolean >
44+ ) {
45+ /* //unoptimized version:
46+ val oneOfMyClasses = workingList[0]::class
47+ val firstSelector = oneOfMyClasses.getPropertyToSortBy(shiurFilterOptions[0])
48+ val compareBy = getComparator(ascending, firstSelector, shiurFilterOptions, oneOfMyClasses)
49+ workingList.sortWith(compareBy)*/
50+ // optimized version:
51+
52+ val size = requireNotEmptyAndSameSize(
53+ ascending, sortCriteria, " List of ascending/descending must not be empty" ,
54+ " List of sort criteria must not be empty" ,
55+ " Each sort criteria must be matched with a ascending/descending boolean; size of ascending/descending list: %d, Size of sort criteria list: %d "
56+ )
57+
58+ workingList.sortWith(
59+ PRIVATEgetComparator (
60+ ascending,
61+ PRIVATEgetPropertyToSortBy (sortCriteria[0 ]),
62+ sortCriteria,
63+ size.second
64+ )
65+ )
66+
67+ }
68+
69+ /* *
70+ *
71+ * much faster than passing in sort criteria strings
72+ * * @sample sort(
73+ myList,
74+ listOf(Class::myParameter1 as KProperty1<Class, Comparable<Any>>,
75+ Class::myParameter2 as KProperty1<Class, Comparable<Any>>),
76+ listOf(true, false)
77+ )
78+ * */
79+ inline fun <reified T > sort (
80+ workingList : MutableList <T >,
81+ sortCriteria : List <KProperty1 <T , Comparable <Any >? >>,
82+ ascending : List <Boolean >
83+ ) {
84+ val size = requireNotEmptyAndSameSize(
85+ ascending, sortCriteria, " List of ascending/descending must not be empty" ,
86+ " List of sort criteria must not be empty" ,
87+ " Each sort criteria must be matched with a ascending/descending boolean; size of ascending/descending list: %d, Size of sort criteria list: %d "
88+ )
89+ workingList.sortWith(
90+ PRIVATEgetComparator (
91+ ascending,
92+ sortCriteria[0 ],
93+ sortCriteria,
94+ size.second
95+ )
96+ )
97+ }
98+ /* *
99+ * Wrapper to {@link sort(MutableList<T>,List<String>,List<Boolean>)} using [Map] for a more convenient API
100+ * */
101+ @JvmName(" sortWithClassParameterStrings" )
102+ inline fun <reified T > sort (
103+ workingList : MutableList <T >,
104+ sortCriteriaMappedToAscending : Map <String , Boolean >
105+ ) = sort(
106+ workingList,
107+ sortCriteriaMappedToAscending.keys.toMutableList(),
108+ sortCriteriaMappedToAscending.values.toList()
109+ )
110+
111+ /* *
112+ * Wrapper to {@link sort(MutableList<T>,List<KProperty1<T, Comparable<Any>?>>,List<Boolean>)} using [Map] for a more convenient API
113+ * */
114+ @JvmName(" sortWithListAsReceiverAndMapOfKProperty1" )
115+ inline fun <reified T > MutableList<T>.sort (
116+ map : Map <KProperty1 <T , Comparable <Any >? >, Boolean >
117+ ) = sort(this , map.keys.toList(), map.values.toList())
118+
119+ /* *
120+ * Wrapper to {@link sort(MutableList<T>,List<String>,List<Boolean>)} using [Map] for a more convenient API
121+ * */
122+ @JvmName(" sortWithListAsReceiverAndMapOfString" )
123+ inline fun <reified T > MutableList<T>.sort (
124+ map : Map <String , Boolean >
125+ ) = sort(this , map.keys.toMutableList(), map.values.toList())
126+
127+ /* *
128+ * Wrapper to {@link sort(MutableList<T>,List<KProperty1<T, Comparable<Any>?>>,List<Boolean>)} using [Map] for a more convenient API
129+ * */
130+ inline fun <reified T > sort (
131+ workingList : MutableList <T >,
132+ sortCriteriaMappedToAscending : Map <KProperty1 <T , Comparable <Any >? >, Boolean >
133+ ) = sort(workingList, sortCriteriaMappedToAscending.keys.toList(), sortCriteriaMappedToAscending.values.toList())
134+
135+ /* *
136+ * Creates the [Comparator] which is used to filter a list by multiple criteria
137+ * Can be thought of as a chain resembling something like
138+ * val comparator = compareBy(LaundryItem::speaker).thenBy { it.title }.thenByDescending { it.length }.thenByDescending { it.series }.thenBy { it.language }
139+ * which will then be fed into list.sortedWith(comparator), except the calls to thenBy() and thenByDescending() will also be passed [KProperty1]s
140+ * @param firstSelector used to start the chain of comparators with ascending or descending order;
141+ * should be the first of the list of conditions to be sorted by. The iteration through the sort criteria
142+ * will continue with the sort criteria after [firstSelector]
143+ * Would make explicitly private but Kotlin throws an error
144+
145+ * */
146+ @PublishedApi
147+ internal inline fun <reified T > PRIVATEgetComparator (
148+ ascending : List <Boolean >,
149+ firstSelector : KProperty1 <T , Comparable <Any >? >,
150+ sortCriteria : List <String >,
151+ size : Int
152+ ): Comparator <T > {
153+ var compareBy =
154+ if (ascending[0 ]) compareBy(firstSelector) else compareByDescending(firstSelector)
155+ for (index in 1 until size) {
156+ // unoptimized:
157+ // val isAscending = ascending[index]
158+ // val propertyToSortBy = getPropertyToSortBy<T>(sortCriteria[index])
159+ // compareBy = if (isAscending) compareBy.thenBy(propertyToSortBy) else compareBy.thenByDescending(propertyToSortBy)
160+ // optimized:
161+ compareBy =
162+ if (ascending[index]) compareBy.thenBy(PRIVATEgetPropertyToSortBy (sortCriteria[index])) else compareBy.thenByDescending(
163+ PRIVATEgetPropertyToSortBy (sortCriteria[index])
164+ )
165+
166+ }
167+ return compareBy
168+ }
169+
170+ /* *
171+ * Would make explicitly private but doing so would violate Kotlin access restrictions
172+ * */
173+ @JvmName(" comparatorWithExplicitKPropertylist" )
174+ @PublishedApi
175+ internal fun <T > PRIVATEgetComparator (
176+ ascending : List <Boolean >,
177+ firstSelector : KProperty1 <T , Comparable <Any >? >,
178+ sortCriteria : List <KProperty1 <T , Comparable <Any >? >>,
179+ size : Int
180+ ): Comparator <T > {
181+ var compareBy =
182+ if (ascending[0 ]) compareBy(firstSelector) else compareByDescending(firstSelector)
183+ for (index in 1 until size) {
184+ compareBy =
185+ if (ascending[index]) compareBy.thenBy(sortCriteria[index]) else compareBy.thenByDescending(
186+ sortCriteria[index]
187+ )
188+
189+ }
190+ return compareBy
191+ }
192+
193+ /* *
194+ * Would make explicitly private but doing so would violate Kotlin access restrictions
195+ * @return null if no parameter was found
196+ * */
197+ @PublishedApi
198+ internal inline fun <reified T > PRIVATEgetPropertyToSortBy (
199+ sortCriterion : String
200+ ): KProperty1 <T , Comparable <Any >? > =
201+ (T ::class as KClass <* >).memberProperties.find { it.name == sortCriterion } as KProperty1<T, Comparable<Any>?>?
202+ ? : throw IllegalArgumentException (" Parameter \" $sortCriterion \" not found" )
203+
204+ }
0 commit comments