Skip to content

Sort operation #76

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 8, 2022
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.native.enableDependencyPropagation=false
kotlin.mpp.stability.nowarn=true

org.gradle.jvmargs=-Xmx2560m
org.gradle.daemon=true
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import array.Distinct
import array.Find
import array.Size
import array.Sort
import operation.FunctionalLogicOperation
import operation.StandardLogicOperation
import string.Capitalize
Expand All @@ -18,14 +19,15 @@ object OperationsProvider {
"length" to Length,
"lowercase" to Lowercase,
"uppercase" to Uppercase,
"toArray" to ToArray,

// time
"currentTime" to CurrentTimeMillis,

// array
"size" to Size,
"sort" to Sort,
"distinct" to Distinct,
"toArray" to ToArray,

"reverse" to Reverse
)
Expand Down
52 changes: 52 additions & 0 deletions operations-stdlib/src/commonMain/kotlin/array/Sort.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package array

import operation.StandardLogicOperation
import utils.asDoubleList
import utils.asList
import utils.secondOrNull

object Sort : StandardLogicOperation {
override fun evaluateLogic(expression: Any?, data: Any?): Any? =
with(expression.asList) {
val elementsToSort = firstOrNull()?.asList
val sortingMode = (secondOrNull() as? String).toSortOrder()

elementsToSort?.sortByMode(sortingMode)
}

private fun String?.toSortOrder() = when (this) {
"desc" -> SortOrder.Descending
"asc" -> SortOrder.Ascending
else -> SortOrder.Unknown
}

private fun List<Any?>.sortByMode(sortingMode: SortOrder) = when {
containsOnlyElementsOfType<String>() -> this.castAndSortComparable<String>(sortingMode)
containsOnlyElementsOfType<Boolean>() -> this.castAndSortComparable<Boolean>(sortingMode)
containsOnlyElementsOfType<Number>() -> asDoubleList.filterNotNull().sortComparable(sortingMode)
else -> null
}

private inline fun <reified T> List<Any?>?.containsOnlyElementsOfType() =
this?.filterIsInstance<T>()?.size == this?.size

@Suppress("UNCHECKED_CAST")
private inline fun <reified T : Comparable<T>> List<*>.castAndSortComparable(sortingMode: SortOrder) =
(this as? List<T>)?.sortComparable(sortingMode)

private inline fun <reified T : Comparable<T>> List<T>.sortComparable(sortingMode: SortOrder) =
modeBasedSort(sortingMode = sortingMode, ascSort = { sorted() }, descSort = { sortedDescending() })

private fun modeBasedSort(sortingMode: SortOrder, ascSort: (() -> Any?), descSort: (() -> Any?)) =
when (sortingMode) {
SortOrder.Descending -> descSort()
SortOrder.Ascending -> ascSort()
SortOrder.Unknown -> null
}
}

private sealed class SortOrder {
object Descending : SortOrder()
object Ascending : SortOrder()
object Unknown : SortOrder()
}
180 changes: 180 additions & 0 deletions operations-stdlib/src/commonTest/kotlin/array/SortTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package array

import JsonLogicEngine
import JsonLogicResult.Failure
import JsonLogicResult.Success
import TestInput
import io.kotest.core.spec.style.FunSpec
import io.kotest.datatest.withData
import io.kotest.matchers.shouldBe

class SortTest : FunSpec({
val operatorName = "sort"
val logicEngine = JsonLogicEngine.Builder().addStandardOperation(operatorName, Sort).build()

withData(
nameFn = { input ->
"Should evaluated $operatorName expression with given ${input.data} result in ${input.result}"
},
ts = listOf(
TestInput(
expression = mapOf(
operatorName to listOf(
mapOf(
"map" to listOf(
mapOf("var" to "integers", "var" to "integers"),
mapOf("%" to listOf(mapOf("var" to ""), 3)),
mapOf("var" to "integers", "var" to "integers"),
)
), "desc"
)
),
data = mapOf("integers" to listOf(1, 2, 3, 4, 5)),
result = Success(listOf(2.0, 2.0, 1.0, 1.0, 0.0))
),
TestInput(
expression = mapOf(
operatorName to listOf(
mapOf(
"map" to listOf(
mapOf("var" to "integers"),
mapOf("%" to listOf(mapOf("var" to ""), 2))
)
), "asc"
)
),
data = mapOf("integers" to listOf(1, 2, 3, 4, 5)),
result = Success(listOf(0.0, 0.0, 1.0, 1.0, 1.0))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, 2, 3))),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1.0, 2, 3.5), "desc")),
result = Success(listOf(3.5, 2.0, 1.0))
),
TestInput(
expression = mapOf(operatorName to listOf(0, "desc")),
result = Success(listOf(0.0))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(0.01, 0.01, 0.001), "desc")),
result = Success(listOf(0.01, 0.01, 0.001))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf("0.1", "0.01", "0.001"), "desc")),
result = Success(listOf("0.1", "0.01", "0.001"))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, "true", 3), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, 3, null), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, 2), listOf(3, 4), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, "2", 3), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, 2, 3), "asc")),
result = Success(listOf(1.0, 2.0, 3.0))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(1, 2, 3), "desc")),
result = Success(listOf(3.0, 2.0, 1.0))
),
TestInput(
expression = mapOf(operatorName to "banana"),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf("banana", "apple", "strawberry"), "asc")),
result = Success(listOf("apple", "banana", "strawberry"))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf("banana", 2, "strawberry"), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf("banana", null, "strawberry"), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to null),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to true),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to emptyList<Any>()),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(null, mapOf("var" to ""))),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(emptyList<String>(), mapOf("var" to ""))),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(false, false), mapOf("var" to ""))),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true, false), "asc")),
result = Success(listOf(false, true))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true), "asc")),
result = Success(listOf(true))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(false), "asc")),
result = Success(listOf(false))
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true, null), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true, "1"), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true, "true"), "asc")),
result = Failure.NullResult
),
TestInput(
expression = mapOf(operatorName to listOf(listOf(true, false), "desc")),
result = Success(listOf(true, false))
),
TestInput(
expression = mapOf(
operatorName to listOf(
mapOf("var" to "fruits"),
mapOf("==" to listOf(mapOf("var" to ""), "pineapple"))
)
),
data = mapOf("fruits" to listOf("apple", "banana", "pineapple")),
result = Failure.NullResult
),
)
// given
) { testInput: TestInput ->
// when
val evaluationResult = logicEngine.evaluate(testInput.expression, testInput.data)

// then
evaluationResult shouldBe testInput.result
}
})