Skip to content

Commit 1927609

Browse files
Add sorting delegate
1 parent 83fdb4c commit 1927609

1 file changed

Lines changed: 204 additions & 0 deletions

File tree

src/main/kotlin/SortingDelegate.kt

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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

Comments
 (0)