I have a functionality which expects aliases for certain operations
For example - count_mean has alias mean and countDistinct has alias approxCountDistinct
"count_missing", "countMissing" -> this.countMissing.toDouble().right()
"count" -> this.count.toDouble().right()
"count_populated", "countPopulated" -> this.countPopulated.toDouble().right()
"count_distinct", "countDistinct", "approxCountDistinct" -> this.countDistinct.toDouble().right()
"count_mean", "mean" -> this.getDecimalData("mean").right()
I want to create an Enum class or something else to efficiently separate all the types of metrics in another file.
Right now I was thinking of Enum class
enum class FieldProfileMetrics(val value: String) {
CountDistinct("countDistinct", "approxDistinctCount", "count_distinct")
.
.
}
Followed by using the enum class like this, which would return the appropriate metric from Enum (using the aliases to check)
val metric = FieldProfileMetrics.valueOf(metricName)
Any help to figure a better way to structure this is appreciated
I recommend make aliasOf method.
This is sample code.
enum class FieldProfileMetrics {
Unknown,
CountMissing,
Count,
CountPopulated,
CountDistinct,
CountMean;
companion object {
fun aliasOf(value: String): FieldProfileMetrics {
return when(value) {
"count_missing", "countMissing" -> CountMissing
"count" -> Count
"count_populated", "countPopulated" -> CountPopulated
"count_distinct", "countDistinct", "approxCountDistinct" -> CountDistinct
"count_mean", "mean" -> CountMean
else -> Unknown
}
}
}
}
and you use the method.
val metric = FieldProfileMetrics.aliasOf(metricName)
Related
Consider this extreme simplified code (available on https://pl.kotl.in/bb2Irv8dD):
sealed class Person {
data class A(val i: Int) :
Person()
}
fun main() {
val a = Person.A(i = 0)
val b = Person.A(i = 1)
// Compiles
when (a) {
is Person.A -> print("I have access to {$a.i}")
}
// Does not compile :(
when (a to b) {
is Person.A to is Person.A -> print("I have access to {$a.i} and b {$b.i}")
}
}
Why does the (a to b) code not work? It works for 1 variable, I was hoping I can match on both classes and get both inner values.
The error is:
Incompatible types: Person.A and Pair<Person.A, Person.A> Expecting
'->' Expecting an element Incompatible types: Person.A and
Pair<Person.A, Person.A>
Aside from that syntax not being supported (you can only use is on one thing in a when branch), by using to you're literally creating an instance of the Pair class.
Pair uses generics for the types of its two variables, so this type information is lost at runtime due to type erasure.
So although, you can do this:
when (a to b) {
is Pair<Person.A, Person.A> -> print("I have access to {$a.i} and b {$b.i}")
}
it is only allowed when both a and b are local variables whose types are declared locally, so that the generic types of the Pair are known at compile time. But this makes it mostly useless, because if a and b are local variables with known type at compile time, then you could just replace the above with true or false.
To be able to do something like this in a general way, you must either create local variables to use:
val aIsTypeA = a is Person.A
val bIsTypeA = b is Person.A
when (aIsTypeA to bIsTypeA) {
true to true -> //...
//...
}
or use when without a subject and put the full condition on each branch:
when {
a is Person.A && b is Person.A -> //...
//...
}
The (a to b) returns a Pair<Person.A,Person.A> but what you are checking is Type Person.A to Type Person.A instead of the Type Pair<Person.A,Person.A>.
What you can do instead is:
when (a to b) {
is Pair<Person.A,Person.A> -> print("I have access to {$a.i} and b {$b.i}")
}
From the Kotlin Fundamentals course, we have this code:
#BindingAdapter("sleepImage")
fun ImageView.setSleepImage(item: SleepNight?) {
item?.let {
setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
}
In other languages I would simplify this by using the sleepQuality integer to look up the matching element, in Typescript for example:
setImageResource(R.drawable[`ic_sleep_${item.sleepQuality}`] ?? R.drawable.ic_sleep_active)
To start trying this out even my first step doesn't compile:
0 -> R.drawable["ic_sleep_0"] // doesn't compile
Is this kind of operation possible in Kotlin?
Edit/Update
There's a few good responses here.
It looks like for this specific use case, I can look up resources by string, similar to what I'm trying:
val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)
However, this is not a general solution. The following does not work:
val x = item['sleepQuality']
As noted in some responses, this may be possible using reflection. How would this be done?
val resId = context.resources.getIdentifier("ic_sleep_${item.sleepQuality}", "drawable", context.packageName)
setImageResource(if (resId != 0) resId else R.drawable.ic_sleep_active)
Through reflection (based on Getting value of public static final field/property of a class in Java via reflection) :
val resId = try {
R.string::class.java.getField("ic_sleep_${item.sleepQuality}").getInt(null)
} catch (e: Exception) {
R.string.ic_sleep_active
}
setImageResource(resId)
Only using reflection. Kotlin statically typed programming language and does not support "Variable variables"
I have a class called Column<E> that delegates to a MutableList<E>.
To sort the Comparable elements ("e") of columns without providing a comparator, I pass a reified type argument ("type") to determine whether e implements comparable using reflection and then use e's compareTo method to construct a comparator. This all works fine.
I also have a function object called AggregateFunction that is used in reduce operations. AggregateFunction holds an actual function (to do the reduction operation), and a name (for programmatically creating a name for the result). There are several subtypes of AggregateFunction. NumericAggregateFunction, for example, takes an input column of type Column and always returns a Double.
The typical use case is to partition the input data into subgroups and return a Column containing the computed values for each subgroup. The catch is that I want to programmatically construct a column to hold the results. In the case of NumericAggregateFunction, I want to create a Column<Double>. For BooleanAggregateFunction, a Column<Boolean>, etc.
If I want Aggregate function to return a MutableList<Double> I can create it without a problem using:
fun resultList() : MutableList<OUT> {
return ArrayList<OUT>()
}
However, the same approach fails to compile for Column, apparently because of the reified type. If I attempt to use the inline function, e.g.
fun resultColumnA() : Column<OUT> {
return Column<OUT>("column name")
}
I get:
Cannot use 'OUT' as reified type parameter. Use a class instead.
I also attempted to call the primary constructor directly, passing in the type parameter as shown below, it also fails to compile:
fun resultColumn() : Column<OUT> {
return Column<OUT>(
inputColumn!!.type,
"column name")
}
I now get the error:
Type mismatch. Required: OUT Found: Any!
Finally, I tried reifying the type parameter in the context of the Aggregate function, adding these two methods:
inline fun <reified OUT> col(nm:String) =
Column(
OUT::class.java,
nm
)
fun resultColumnB() : Column<OUT> {
return this.col("name")
}
But the line return this.col("name") results in a compile time error:
Cannot use 'OUT' as reified type parameter. Use a class instead.
Is there a way to create a Column similar to how the MutableList was created?
If not, is there a way to determine whether the elements of a MutableList are comparable without using a reified type? If I didn't have to do that I could get rid of the type entirely.
Partial Implementation of class Column is included below
package com.fathom.core.tables
inline fun <reified E> Column(nm:String) =
Column(
E::class.java,
nm
)
open class Column<E>(val type: Class<E>, var name: String, val comparator : Comparator<E>? = null, val elements: MutableList<E?> = ArrayList()) : MutableList<E?> by elements{
var formatter: (E?) -> String = { e ->
if (e == null) "" else e.toString()
}
// when present, allows sorting on this vector without providing a comparator to the sort method
var defaultComparator: Comparator<E>? = null
/**
* Returns true if elements contained in this column implement comparable.
* That makes the column sortable
*/
fun isComparable(): Boolean {
return type.interfaces.contains(Comparable::class.java)
}
/**
* Returns an int comparator where the ints are the indexes of the elements of the column rather than the elements.
* It uses the indexes to get the values, which are then compared using
* (a) a Comparator<E> column property named 'comparator', or
* (b) the natural comparator for any column that implements Comparable
*
* #throws UnsupportedOperationException if the column has no comparator and doesn't implement Comparable
*/
#Suppress("UNCHECKED_CAST")
fun rowComparator() : Comparator<Int> {
if (comparator != null) {
return Comparator { r1, r2 ->
val v : E = get(r1) as E
val f1 : E = this[r1] as E
val f2 : E = this[r2] as E
comparator.compare(f1, f2)
}
}
if (!isComparable()) {
throw UnsupportedOperationException(
"Columns that are used in table sorts must either " +
"provide a comparator or contain elements that " +
"implement comparable"
)
}
return Comparator { r1, r2 ->
val v : E = get(r1) as E
val f1 : Comparable<E> = this[r1] as Comparable<E>
val f2 : E = get(r2) as E
f1.compareTo(f2)
}
}
}
I'm trying to have this compiling:
val criteriaList = aList.stream().map { dateRange -> {
Criteria.where("KEY").`is`(dateRange) } }.toList().toTypedArray()
Criteria().orOperator(*criteriaList)
But:
Criteria().orOperator(*criteriaList)
Currently does not compile:
Type mismatch.
Required:
Array<(out) Criteria!>!
Found:
Array<(() → Criteria!)!>
Why?
You are mapping your dateRange to a () -> Criteria.
You do not need to wrap what is following after -> with curly braces. Check also the Kotlin reference regarding Lambda expression syntax:
val sum = { x: Int, y: Int -> x + y }
A lambda expression is always surrounded by curly braces [...], the body goes after an -> sign. If the inferred return type of the lambda is not
Unit, the last (or possibly single) expression inside the lambda body is treated as the return value.
So you could just write the following instead:
.map { dateRange -> Criteria.where("KEY").`is`(dateRange) }
Note also that you do not really need to call stream(), but you can directly call map on it (except it wouldn't be a real List in the first place).
So your code could probably be simplified to something like:
val criteriaList = aList.map { dateRange -> Criteria.where("KEY").`is`(dateRange) }
.toTypedArray()
or
val criteriaList = aList.map { Criteria.where("KEY").`is`(it) }
.toTypedArray()
I have a (simplified) table structure that is defined like this:
data class Column<T>(val name: String, val value: T)
data class Row(val data: List<Column<*>>)
data class Grid(val rows: List<Row>)
I now want to calculate the totals for each column in that grid, i.e. the ith element of each row needs to be accumulated.
My solution looks like this. I simply flatMap the data and group the column values by the column's name, which I then fold to the corresponding sums.
private fun calculateTotals(data: Grid) = data.rows
.flatMap(Row::data)
.groupingBy(Column<*>::name)
.fold(0.0) { accumulator, (_, value) ->
accumulator + when (value) {
is Number -> value.toDouble()
else -> 0.0
}
}
I could not come up with a better solution. I think yours is really good, but I would suggest some syntactic improvements.
Use lambda references
Use destructuring syntax
Don't use when, if you only test for one specific type, use the safe cast operator (as?), the safe call operator (?) and the elvis operator (:?).
private fun calculateTotals(data: GridData) = data.rows
.flatMap(RowData::data) // 1
.groupingBy(ColumnsData<*>::column) // 1
.fold(0.0) { accumulator, (_, value) -> // 2
accumulator + ((value as? Number)?.toDouble() ?: 0.0) // 3
}