I am working with some datasets in my room database, and my method is to have one table with information about a dataset called DatasetInfo which stores things like name, type of value stored, id, etc; and a second table where I store the values in 3 columns: (id, date, value). This ordered triplet is defined as a DatasetValue entity. Here, (date, value) is an ordered pair that I want to plot.
To plot these ordered pairs, I have to convert them to a list of Entry objects, where Entry takes the values x and y. It makes the most sense to query my database and simply ask for List<Entry>, because right now I ask for List<DatasetValue> and then I have to map that result to List<Entry> which is unnecessary.
I query for the dataset information table DatasetInfo as follows:
data class DatasetWithValues(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
entity = DatasetValue::class,
)
var values : List<Entry>
)
Now, as I said above, Entry has values x and y, and Dataset calls them date and value. Of course, when I ask for this relation, it will fail because it doesn't know how to assign values from a table with the columns id, date, and value to an object which takes x and y. So, I define a new class:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(date, value)
and then make the following adjustment:
//var values : List<Entry>
var values : List<DatasetEntry>
That does nothing. The code doesn't compile because:
SQL error or missing database (no such column: x)
Well, what if I instead write:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(){
init{
x = date
y = value
}
}
That doesn't help either, same error. Even if I remove that init call, it still wants x.
The plot thickens, because inside of Entry I can see x is declared private. So I have absolutely no clue what is happening here. How does Room even know to look for x? Is there any work around for this other than renaming the columns in my table to x and y?
Is there any work around for this other than renaming the columns in my table to x and y?
If you have such option it would be the easiest. Still there are some options you could consider:
1. Mapping Room's result to needed one
So, you ask Room for some raw result and then map it to ready one. For that you add 2 classes:
data class DatasetWithValuesRaw(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
)
var values : List<DatasetValue>
)
data class DatasetWithValuesReady(
var datasetInfo: DatasetInfo,
var values : List<Entry>
)
Let's say you have a dao method:
Query("select * ....")
fun getRawData(): List<DatasetWithValuesRaw>
For mapping you use:
fun getReadyData() =
getRawData().map { item ->
DatasetWithValuesReady(item.datasetInfo,
item.values.map { Entry(x = it.date, y = it.value)
}) }
2. Replacing Room's #Relation with explicit query
It's not what you really want, but still is an option.
Use class like that:
data class DatasetWithSeparateValues(
#Embedded
var datasetInfo: DatasetInfo,
#Embedded
var value : Entry // <----- it's not a list, just a single value
)
and in your dao you set query with explicit columns' names (x and y). Something like that:
Query("SELECT *, values.date as x, values.value as y FROM dataset LEFT JOIN values on dataset.DATASET_COLUMN_DATASET_ID = values.VALUES_COLUMN_ID")
fun getData(): List<DatasetWithSeparateValues>
As a result you'll get a list, but if there is a one dataset with 5 values you'll get inside list 5 items with the same dataset and separate values. After that you could use Kotlin collection's methods (groupBy for example) to prettify result in some way
Related
I have list of three set of values which are related to each other. i.e. Roll Number, Student Name and School Name.
I am using Kotlin Triple to store them.
Below is the code:
val studentData = listOf(
Triple(first = "1", second ="Sam", third = "MIT"),
Triple(first = "2", second ="Johnny", third = "SYM"),
Triple(first = "3", second ="Depp", third = "PIT")
)
And now I need to build a function which will accept roll number and will return either student name or school name. Something like below:
fun getStudentDetails(rollNumber: String) : String {
//...
//return student name or school name
}
How to achieve this?
How to traverse the Triple in most preformat way considering below:
a) the time and space complexity
b) the list of student details can grow large
Given that rollNumber is a String you can just filter the list using firstOrNull and return the student name or school name:
fun getStudentDetails(rollNumber: String) : String =
studentData.firstOrNull({ (roll, _, _) ->
roll == rollNumber
})?.second ?: "No student with $rollNumber"
The space complexity would be constant, the time complexity is O(n) at the worst case.
There is a data class as fruits.
data class Fruits(
val code: String, //Unique
val name: String
)
The base list indexed items with boolean variable is as below.
val indexList: MutableList<Boolean> = MutableList(baseFruitList.size) { false }
Now the Favourite Indexed list is as below
val favList: MutableList<Boolean> = MutableList(favFruitList.size) { true}
I want a combined full list which basically has the fav item indicated as true.
Ex:
baseFruitList = {[FT1,apple],[FT2,grapes],[FT3,banana],[FT4,mango],[FT5,pears]}
favList = {[FT2,grapes],[FT4,mango]}
The final index list should have
finalIndexed = {false,true,false,true,false}
How can we achieve in Kotlin, without iterating through each element.
You can do
val finalIndexed = baseFruitList.map { it in favList }
assuming, like #Tenfour04 is asking, that name is guaranteed to be a specific value (including matching case) for a specific code (since that combination is how a data class matches another, e.g. for checking if it's in another list)
If you can't guarantee that, this is safer:
val finalIndexed = baseFruitList.map { fruit ->
favList.any { fav.code == fruit.code }
}
but here you have to iterate over all the favs (at least until you find a match) looking to see if one has the code.
But really, if code is the unique identifier here, why not just store those in your favList?
favList = listOf("FT2", "FT4") // or a Set would be more efficient, and more correct!
val finalIndexed = baseFruitList.map { it.code in favList }
I don't know what you mean about "without iterating through each element" - if you mean without an explicit indexed for loop, then you can use these simple functions like I have here. But there's always some amount of iteration involved. Sets are always an option to help you minimise that
I'm trying to build a simple app that would display a coin collection for each user I type into a textfield. The coin collection would have to be unique to each user. I already have the repository for the coins. How do I generate a new random coin collection for each user? Each collection could have multiple coins of the same value but with different years.
object CoinRepository {
fun getCoinCollection(): List<Coin> {
return listOf(
Coin(
id = 1,
name = "Penny",
year = (1900..2022).random()
),
Coin(
id = 2,
name = "Nickel",
year = (1900..2022).random()
),
Coin(
id = 3,
name = "Dime",
year = (1900..2022).random()
),
Coin(
id = 4,
name = "Quarter",
year = (1900..2022).random()
),
Coin(
id = 5,
name = "Dollar",
year = (1900..2022).random()
)
)
}
}
data class Coin(
val id: Int,
val name: String,
val year: Int
)
You could do something like this:
import kotlin.random.Random
// Define your specific data in an enum, with all the relevant properties
enum class Denomination(val id: Int, val label: String) {
PENNY(1, "Penny"),
NICKEL(2, "Nickel"),
DIME(3, "Dime"),
QUARTER(4, "Quarter"),
DOLLAR(5, "Dollar");
companion object {
// a simple way to return one of the instances at random - the property
// avoids creating a new values() array every time it's called
val values = values()
fun random() = values.random()
}
}
// a basic way to keep the random date logic in the Coin class itself, using
// a default parameter. No validation involved obviously!
data class Coin(val id: Int, val label: String, val year: Int = (1900..2022).random())
// get a random number of Coins, within a certain min/max
fun getCoinCollection() = List(Random.nextInt(1, 10)) {
// pulls a random coin type and creates a Coin, letting its constructor
// handle the random date (you could do it here if you want)
Denomination.random().run { Coin(id, label) }
}
There's more than one way to organise it, I've thrown a few things in there so you can get some ideas of how you might do it. But it's basically a function that creates a list of random length (within limits), and then creates a Coin for each item, using a random Denomination
The Denomination enum is just a way to define your data, a fixed set of possible items with certain properties. Because enums generate that values() array automatically (containing all its instances) you can easily pick one at random. You could also extend the properties here to include a valid date range for each coin type, etc
You could just automatically generate the label and id values from the enum's name and ordinal properties (e.g. "PENNY" and 0) so you don't need to declare them explicitly - I feel like it's usually a good idea to decouple the data from how it's represented in the enum in code, but that's your call - I've included it so you can see how
I have a map of collections . I need to get a list of ids from that..
val m1 = mapOf("id" to 1, "name" to "Alice")
val m2 = mapOf("id" to 2, "name" to "Bob")
val m3 = mapOf("id" to 3, "name" to "Tom")
val nameList = listOf(m1, m2, m3)
The result shall be [1, 2, 3]
Assuming you want a list as per the example, not a map as per the title, I would do it like this:
val result = nameList.map {
it.getValue("id").also { id ->
require(id is Int) { "id must be an Int" }
} as Int
}
This has the advantage of handling the following errors cleanly:
The id key is missing: NoSuchElementException: Key id is missing in the map
The id value is not an Int: IllegalArgumentException: id must be an Int
First things first, I believe that if you can, you should use classes instead of maps for storing heterogeneous data like this. So instead of your maps, you can use:
data class Person(val id: Int, val name: String)
val m1 = Person(id = 1, name = "Alice")
val m2 = Person(id = 2, name = "Bob")
val m3 = Person(id = 3, name = "Tom")
val list = listOf(m1, m2, m3)
val idsList = list.map { it.id } // no error handling required, rely on the type system
Now, if you really want to use maps like that, you have several options.
If you're certain the id key will be present and its value will be an Int, you can use the following:
nameList.map { it["id"] as Int }
This will fail with NullPointerException if id is not present in one of the maps or with ClassCastException if it's not an Int.
Normally you should make sure your map matches your contract at creation time, and not when accessing this kind of information.
But if you need to handle errors here for some reason, you can use the following instead:
nameList.map {
(it.getValue("id") as? Int) ?: error("'id' is not an Int")
}
getValue fails on absent keys with NoSuchElementException, and the error() call fails with IllegalStateException. You can also use other kinds of exceptions using throw or require().
If you want to just ignore the entries that don't have a valid integer id, you can use the following:
nameList.mapNotNull { it["id"] as? Int }
If you want to ignore the entries that don't have an id, but fail on those who have a non-integer id, you can use this:
nameList.mapNotNull { map ->
map["id"]?.let { id ->
(id as? Int) ?: error("'id' is not an Int")
}
}
These 2 last examples rely on mapNotNull, which filters the elements out if their mapped value is null.
When creating a table query, I would like to modify my select statement by mapping the default table query. However, I cannot find a way to map the value of a column and still map to my case class
case class MyRecord(id: Int, name: String, value: Int)
class MyTable(tag: Tag) extends Table[MyRecord](tag, "MYTABLE") {
def id = column[Int]("id")
def name = column[String]("name")
def value = column[Int]("value")
def * = (id, name, value) <> (MyRecord.tupled, MyRecord.unapply)
}
lazy val tableQuery = TableQuery[MyTable]
I would like to trim the value of name with this function:
def trimLeading0: (Rep[String]) => Rep[String] = SimpleExpression.unary[String, String] {
(str, queryBuilder) =>
import slick.util.MacroSupport._
import queryBuilder._
b"TRIM(LEADING 0 FROM $str)"
}
Now I am at a loss about what to do here:
val trimmedTableQuery: Query[MyTable, MyRecord, Seq] = tableQuery.map(s => ???)
I have tried mapping the Rep like I would do with a case class:
val trimmedTableQuery = tableQuery.map(s => s.copy(name = trimLeading0(s.name)))
This refuses to compile with value copy is not a member of MyTable
My current workaround is to use a custom function instead of MyRecord.tupled for the default projection:
def trimming(t: (Int, String, Int)) = MyRecord(t._1, t._2.dropWhile(_ == "0"), t._3)
def * = (id, name, value) <> (trimming, MyRecord.unapply)
Alternatively, I could map the returned result of the DBIOAction returning a tuple to the case class, which is much less elegant:
val action = tableQuery.map{ s => (s.id, trimLeading0(s.name), s.value)}.result
val futureTuples: Future[Seq[(Int, String, Int)]] = db.run(action)
val records = futureTuples map (s => s.map(MyRecord.tupled))
But how can I do it inside the map method while building the query? OR would it be better to change the def name column description?
You can't mess with the default projection (i.e. def *) in MyTable as it needs to be symmetric. It's used for query and insert. But you can create a trimmedTableQuery based on a specialisation of MyTable with an overridden default projection. Then you can also have tableQuery based on the symmetric default projection. You will get an error if you try to do inserts based on the trimmedTableQuery (but you shouldn't need to do that, just use tableQuery for inserts).
lazy val tableQuery = TableQuery[MyTable]
lazy val trimmedTableQuery = new TableQuery(new MyTable(_) {
override def * = (id, trimLeading0(name), value) <> (MyRecord.tupled, MyRecord.unapply)
})