creating a random list of coins from a repository - kotlin

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

Related

Combining Two List in Kotlin with Index

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

How to resort a mutableStateOf(listOf<PojoObject>())?

I have a populated:
data class PojoObject(val id: Int, val name: String)
val listData = mutableStateOf(listOf<PojoObject>())
//..
listData.value = anotherList.map{//..}
Assuming listData is already sorted by increasing id.
How can I resort the listData object by lets say name in alphabetic order?
I would prefer to operate on listItem directly without wrapping it like:
listData.value = listData.component1().map{//..}.sort{//..}

Sum Mutable Map values inside a list of objects kotlin

I have a data class in Kotlin like this:
data class Activity(
var id: String? = "",
var prize: MutableMap<String?, Int?>? = null
)
And a list of this object:
var myList = listOf(Activity("A", prize={day_5=70, day_4=70}),
Activity("B", prize={day_5=40, day_4=80}))
The desired result is:
Activity("A", prize={total=140}),
Activity("B", prize={total=120})
So basically I want to sum the values of the prize map inside of each object.
I think that has something to do with transformation but I'm new to Kotlin and I couldn't find any resources over the internet, or maybe they were to complicated.
This algorithm is easy, just iterating through all the maps and replace elements with the sum:
val myList = listOf(
Activity("A", hashMapOf("day_5" to 70, "day_4" to 70)),
Activity("B", hashMapOf("day_5" to 40, "day_4" to 80)),
)
for (i in myList) {
val total = i.prize.values.sum()
i.prize.clear()
i.prize["total"] = total
}

Kotlin iterate a collection and map a certain value to new map

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.

Room Relation Specify Column Names When Extending

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