I have some list of objects. Each of them contains specific "from" and "to" time range specified.
So for example:
import org.joda.time.DateTime
data class MyObject(
val from: String?,
val to: String?
)
The asUtcDateTime() is just my extension method that converts the given String do DateTime
How can I find the nearest object which:
is not in today time range
will be closest from today (future or past)?
What I've tried so far is just to get the nearest MyObject from the past and future like so:
val now = DateTime.now()
val nearestPastSchedule = allSchedules
.sortedBy { it.to.asUtcDateTime() }
.filter { it.to.asUtcDateTime() != null }
.lastOrNull { it.to.asUtcDateTime()!!.millis < now.withTimeAtStartOfDay().millis }
val nearestFutureSchedule = allSchedules
.sortedBy { it.from.asUtcDateTime() }
.filter { it.from.asUtcDateTime() != null }
.lastOrNull { it.from.asUtcDateTime()!!.millis > now.withTimeAtStartOfDay().millis }
Don't know what would be good solution in terms of comparing them (considered that there are nullable) and also have the actual MyObject returned for each of them
Instead of sorting, you can find the specified element yourself. I do so by finding the absolute minimum difference between now and the time specified in the object.
For simplicity reasons, I adjusted the data class to use ZonedDateTime (assuming Java >=8 to be available):
data class MyObject(
val from: ZonedDateTime?,
val to: ZonedDateTime?
)
With that, you can filter and find the minimum absolute value between now and the corresponding time:
val nearestPastSchedule =
allSchedules.filter { it.to != null }
.minBy { abs(it.to!!.toInstant().toEpochMilli() - now) }
val nearestFutureSchedule =
allSchedules.filter { it.from != null }
.minBy { abs(it.from!!.toInstant().toEpochMilli() - now) }
Related
I receive data from Request information as list data (List) below code. That data has a "key" parameter by which I want to sort it.
data class ApplianceSetting(
#SerializedName("key") val key: String,
#SerializedName("value") var value: Any,
(...)
I have the required order in the SettingsUtilEnum and want to sort items by that.
After that, I can convert the list using map{} the data and use the function of Enum getSettingByMode() and get the list of Enum values. Then I will sort them and convert them again to List.
But that sounds too inefficient. Is there a better way.
enum class SettingsUtilEnum(
var settingKey: String,
override val order: Int = 99,
var settingName: String = "",
) : AbstractOrderEnum {
FIRST_MODE("first.mode", 0),
SECOND_MODE("second.mode", 1),
(...)
UNKNOWN_MODE("", 99);
companion object {
#JvmStatic
fun getSettingByMode(settingKey: String): SettingsUtilEnum? {
return values().find { it.settingKey == settingKey }
}
k
private fun initDataObserver() {
(activity as FavouriteActivity).viewModel.applianceSettings.observe(activity as FavouriteActivity
) { data ->
(controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
val adapter = (controlRecyclerView.adapter as FavouriteAdditionalControlsAdapter)
// public final var data: List<ApplianceSetting>
// old code:
// data.settings
adapter.data = sortAndGetControlModes(data)
adapter.notifyDataSetChanged()
}
}
// TODO: sortAndGetControlModes
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.map {
getSettingByMode(it.key)
?: UNKNOWN_MODE.apply {
// If in future new modes are added -> put them as tail
settingKey = it.key
}
}.sortedBy { it.order }
// error i need to return again List<ApplianceSetting>
If you want to compare keys with theirs ASCII values you can just use sortBy { it.key }
If you want to expand possibilities of comparison you can use function sortedWith with passing custom comparator as argument.
Comparator used to compare its two arguments for order. Returns zero if the arguments are equal, a negative number if the first argument is less than the second, or a positive number if the first argument is greater than the second.
Example:
You can use it like that if you want to sort by integer value of key parameter:
data.settings.sortedWith { a, b ->
when {
a.key.toInt() < b.key.toInt() -> -1
a.key.toInt() > b.key.toInt() -> 1
else -> 0
}
}
I fixed it using sortedBy and as comparator I am using received value (order) from getSettingByMode(), if item is not found (null) I give him order value of 99 and put it on tail position:
private fun sortAndGetControlModes(data: ApplianceSettingsList) =
data.settings.sortedBy {
getSettingByMode(it.key)?.order ?:99
}
I'm new to Kotlin and trying to figure out how I can do the best way. I have an api call that I call and I convert the response to a list of objects:
data class JobAd(
val published: LocalDate?,
val title: String?,
val jobtitle: String?,
val description: String?
)
On the api call, I search for all job ads that are from today and back in time of 6 months. For example I get all objects which is from LocalDate.now() and 6 months back LocalDate).now().minusMonths(6). I want to iterate through all the objects and see if 2 random words (java and kotlin) are contained in the object. I want to check either title, jobtitle or description contain the word java or kotlin. I only need one hit of the word java or kotlin in these properties, if title contain java or kotlin, add it to list and check next object. If not title contain the words and either jobtitle, but description does it, add it to the list and check next object. and add it to a list based on which week it is.
I want the output to be like this:
(2022) Week 12 -> Java: 0, Kotlin: 1
(2022) Week 11 -> Java: 0, Kotlin: 0 (If some weeks does not have hit, i want to show to too)
...
(2021) Week 52 -> Java: 1, Kotlin: 2
This is my code so far:
private fun findAdsBasedOnKeyWords(jobAds: MutableList<JobAd>, keywords: List<String>, from: LocalDate, to: LocalDate): MutableMap<Any, MutableMap<String, Any>> {
val resultMap = mutableMapOf<Any, MutableMap<String, Any>>()
val counter = mutableMapOf<String, Any>() //Meta data
for (jobAd: JobAd in jobAds) {
for (keyword: String in keywords) {
val weekNumber = DateParser.getWeekNumber(jobAd.published!!)
// Initialize placeholder data, to fill even empty weeks
resultMap.putIfAbsent(weekNumber, emptyMapOfKeywords(keywords, jobAd.published))
// Validate keyword exist in job ad
val contains = jobAd.toString().lowercase()
.contains(keyword.lowercase()) //Can be an issue if the toString gets overridden
if (contains) {
counter.putIfAbsent(keyword, 0)
counter.compute(keyword) { _, v -> v.toString().toInt() + 1 }
resultMap[weekNumber]!!.compute(keyword) { _, v -> v.toString().toInt() + 1 }
}
}
}
resultMap["total"] = counter
resultMap["period"] = mutableMapOf("from" to from, "to" to to)
logger.info("[{}] matches found", counter)
return resultMap
}
//Helper method to generate placeholder data
private fun emptyMapOfKeywords(keywords: List<String>, published: LocalDate): MutableMap<String, Any> {
val keywordMap = mutableMapOf<String, Any>()
for (keyword in keywords) {
keywordMap.putIfAbsent(keyword, 0)
}
keywordMap.putIfAbsent("from", DateParser.startOfWeekDate(published))//Monday of the week
keywordMap.putIfAbsent("to", DateParser.endOfWeekDate(published))//Sunday of the week
return keywordMap
}
Is there any way to do it better or optimize it and please add comment for why.
It's a pretty extreme anti-pattern to use Maps to hold various types of data that you need to inspect. That's trying to force a strongly typed language to behave like a weakly typed language, losing all the protection you get from using types.
Maps are appropriate when the keys are something you don't know at compile time and you know you'll need to look up items by their keys at runtime.
So instead of a MutableMap<Any, MutableMap<String, Any>> return value, you should create classes for holding results. From what I can tell, you want to return a series of line items for every week in the input range, so you can create a class like this to represent a line item, and then return a simple list of them from your function. You are currently also returning the range, but I don't see what you're using it for so I left it out.
You're working with a week of a year a lot, so I think it will also be helpful to have a class to represent that, along with a couple of functions to help convert from LocalDate.
data class LocalWeek(val year: Int, val week: Int)
fun LocalDate.toLocalWeek() = LocalWeek(year, get(IsoFields.WEEK_OF_WEEK_BASED_YEAR))
/** Gets every week represented in a range of dates. */
fun ClosedRange<LocalDate>.toLocalWeeks() = sequence {
var date = start
val lastExclusive = endInclusive + Period.ofWeeks(1)
while (date < lastExclusive ) {
yield(date.toLocalWeek())
date += Period.ofWeeks(1)
}
}
data class JobAdsSearchLineItem(
val localWeek: LocalWeek,
val keywordHitCountsByKeyword: Map<String, Int>
) {
fun toReadableString() =
"(${localWeek.year}) Week ${localWeek.week} -> " +
keywordHitCountsByKeyword.entries
.joinToString { (word, count) -> "$word: $count" }
}
Using toString() is fragile, like you mentioned in your code comments. I would create a helper function like this to evaluate whether a term is found:
fun JobAd.containsIgnoreCase(str: String): Boolean {
val value = str.lowercase()
return title.orEmpty().lowercase().contains(value)
|| jobtitle.orEmpty().lowercase().contains(value)
|| description.orEmpty().lowercase().contains(value)
}
Since you're using !! on your published date, I'm assuming these values don't need to be nullable. It would be much easier to work with if you make the property non-nullable:
data class JobAd(
val published: LocalDate,
val title: String?,
val jobtitle: String?,
val description: String?
)
Then your search function can be written like this:
private fun findAdsBasedOnKeyWords(
jobAds: List<JobAd>,
keywords: List<String>,
from: LocalDate,
to: LocalDate
): List<JobAdsSearchLineItem> {
// Initialize empty results holders representing every week in the range
// Use an outer map here because we need to keep retrieving the inner maps by
// the week when iterating the input below.
val results = mutableMapOf<LocalWeek, MutableMap<String, Int>>()
for (localWeek in (from..to).toLocalWeeks()) {
results[localWeek] = mutableMapOf<String, Int>().apply {
for (keyword in keywords) {
put(keyword, 0)
}
}
}
for (jobAd in jobAds) {
val weekResults = results[jobAd.published.toLocalWeek()] ?: continue
for (keyword in keywords) {
if (jobAd.containsIgnoreCase(keyword)) {
weekResults[keyword] = weekResults.getValue(keyword) + 1
}
}
}
return results.entries.map { JobAdsSearchLineItem(it.key, it.value) }
}
And to use it you can call this function and use the toReadableString() function to help generate your output from the list of results.
write a method "lastWhere" that accepts a function called "where" of type (T) -> Boolean. The method returns the last element of type T to which the "where" function applies. If no matching element is found, null is returned.
call the method "lastwhere" on the linked list below. Find the last game that is more than 10 euros.
So far I've got this Code going for me.
I assume the only important piece of Code I need to edit is the "fun lastWhere" for task number 1)
the second task wants me to implement a way on the main function to find the last Game that is cheaper than 10 Euros.
class LinkedList<T> {
data class Node<T>(val data: T, var next: Node<T>?)
private var first: Node<T>? = null
override fun toString(): String = first?.toString() ?: "-"
fun isEmpty() = first == null
fun addLast(data: T) {
if (first == null) {
first = Node(data, first)
return
}
var runPointer = first
while (runPointer?.next != null) {
runPointer = runPointer.next
}
runPointer?.next = Node(data, null)
}
fun lastWhere (where: (T) -> Boolean): T? { // "where" function needs to be implemented
if (isEmpty()) return null
else {
var runPointer = first
while (runPointer?.next != null ) {
runPointer = runPointer.next
}
return runPointer?.data
}
}
}
data class Game(val title: String, val price: Double)
fun main() {
val list = LinkedList<Game>()
list.addLast(Game("Minecraft", 9.99))
list.addLast(Game("Overwatch", 29.99))
list.addLast(Game("Mario Kart", 59.99))
list.addLast(Game("World of Warcraft", 19.99))
var test = list.lastWhere ({it.price >= 10.00}) // This is probably wrong too, since I haven't got task 1) working
println (test)
}
Would appreciate any help!
Since you only store a reference to first node, you don't have any choice but to start at first and iterate. you will also have to keep a reference to last item that satisfied the where predicate, and keep updating this reference with every iteration.
fun lastWhere (where: (T) -> Boolean): T? {
var runPointer = first
var item: T? = null // init item to null, if nothing is found we return null
while (runPointer != null ) {
// For every node, execute the where function and if it returns true
// then update the return value
if(where(runPointer.data)) { item = runPointer.data }
runPointer = runPointer.next
}
return item
}
I originally wanted to create a class that can abort instantiation in constructor, but according to this link I should instead use a Factory class. But now I want to prevent anyone except the factory class from creating an object of class "Inner" while giving access to the methods of the inner class to everyone.
I have already tried this answer.
import java.util.Date
object InnerFactory {
class Inner private constructor(startDate: Date? = null, endDate: Date? = null) {
fun getTimeDifference(): Long? {
//calculates time difference but doesn't matter to this example
}
}
fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
if (startDate != null && endDate != null && !endDate.after(startDate)) {
return null
}
return Inner(startDate, endDate)
}
}
I would use it like the following:
val date1 = Date(1547600000)
val date2 = Date(1547600600)
val inner = InnerFactory.createInnerObject(date1, date2) //should return an instance
val invalidInner = InnerFactory.createInnerObject(date2, date1) //should not return an instance because the "endDate" is before "startDate"
val difference = inner?.getTimeDifference()
It says "cannot access '<init>': it is private in 'Inner'" when hovering over my usage of the constructor in the "createInnerObject" function.
What you could do:
introduce an interface Inner with all the necessary functions that should be exposed
make all the class(es) private and implement that interface
Sample:
object InnerFactory {
interface Inner {
fun getTimeDifference(): Long?
}
private class InnerImpl(startDate: Date? = null, endDate: Date? = null) : Inner {
override fun getTimeDifference(): Long? = TODO("some implementation")
}
fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
if (startDate != null && endDate != null && !endDate.after(startDate)) {
return null
}
return InnerImpl(startDate, endDate) // InnerImpl accessible from here but not from outside of InnerFactory...
}
}
Now you can't access InnerImpl from outside anymore, but still have all the necessary functions available:
// the following all work as it deals with the interface
val inner = InnerFactory.createInnerObject(date1, date2) //should return an instance
val invalidInner = InnerFactory.createInnerObject(date2, date1) //should not return an instance because the "endDate" is before "startDate"
val difference = inner?.getTimeDifference()
// the following will not compile:
InnerImpl()
Unfortunately, private members of Kotlin inner classes are not accessible from the outer instance:
private means visible inside this class only
Kotlin reference / Visibility modifiers
However, Java is not this restrictive with its visibility modifiers:
access is permitted if and only if it occurs within the body of the top level type (§7.6) that encloses the declaration of the member or constructor.
Java Language Specification / §6 Names / §6.6 Access Control / §6.6.1 Determining Accessibility
This is one of the only (annoying) cases I have found where Kotlin's rules make a common Java pattern impossible.
The only workarounds (if you want to keep your current structure) would be to rewrite this class in Java, or to expose this constructor with a less restrictive visibility (e.g. internal.)
There was a discussion about this on the Kotlin forums - it seems that this is a JVM limitation, and that it only works in Java because the compiler generates appropriate synthetic accessors.
You could make make the constructor protected. This way you only expose it to subclasses, in this case PrivateClass. Then you will create an instance of PrivateClass or null but return it as InnerClass?.
object InnerFactory {
fun createInnerObject(startDate: Date? = null, endDate: Date? = null): Inner? {
// return null here if conditions are not met
return PrivateClass(startDate, endDate)
}
open class Inner protected constructor(val startDate: Date?, val endDate: Date?) {
fun getTimeDifference(): Long? { /* ... */ }
}
private class PrivateClass(startDate: Date?, endDate: Date?): Inner(startDate, endDate)
}
I'd like to iterate all dates within a given Joda interval:
val interval = Interval(DateTime.now().minusDays(42), DateTime.now())
How to do that in Kotlin?
Heavily inspired by your current solution:
fun Interval.toDateTimes() = generateSequence(start) { it.plusDays(1) }
.takeWhile(::contains)
Usage:
interval.toDateTimes()
.forEach { println(it) }
If you need the LocalDate you could still do the following instead:
interval.toDateTimes()
.map(DateTime::toLocalDate)
.forEach { println(it) }
or as an extension function to Interval again:
fun Interval.toLocalDates() = toDateTimes().map(DateTime::toLocalDate)
If you want the end date to be inclusive instead, use takeWhile { it <= end } instead.
The following extension function gives a Sequence of LocalDate objects from the given Interval, which can be used to iterate those dates.
fun Interval.toLocalDates(): Sequence<LocalDate> = generateSequence(start) { d ->
d.plusDays(1).takeIf { it < end }
}.map(DateTime::toLocalDate)
Usage:
val interval = Interval(DateTime.now().minusDays(42), DateTime.now())
interval.toLocalDates().forEach {
println(it)
}
In this solution, the last day, DateTime.now() is not included in the Sequence since that's how Interval is implemented as well:
"A time interval represents a period of time between two instants. Intervals are inclusive of the start instant and exclusive of the end."
If, for any reason, you want to make it include the last day, just change the takeIf condition to it <= end.
I guess if you need it more than once, it would be better to overload rangeTo operator to allow this syntax
for (i in LocalDate.now() .. LocalDate.now().plusWeeks(1)) {
System.out.print(i) // 2018-08-30 2018-08-31 2018-09-01
}
Here is the code for operator extension:
operator fun LocalDate.rangeTo(other: LocalDate): LocalDateRange {
return LocalDateRange(this, other)
}
And necessary classes:
class LocalDateRange(override val start: LocalDate, override val endInclusive: LocalDate)
: ClosedRange<LocalDate>, Iterable<LocalDate> {
override fun iterator(): Iterator<LocalDate> {
return DateIterator(start, endInclusive)
}
}
class DateIterator(start: LocalDate, private val endInclusive: LocalDate)
: Iterator<LocalDate> {
private var current = start
override fun hasNext(): Boolean {
return current <= endInclusive
}
override fun next(): LocalDate {
current = current.plusDays(1)
return current
}
}
LocalDate is preferred nowadays, so we can simply iterate with day as number:
for (day in minDate.toEpochDay()..maxDate.toEpochDay()) {
// ...
}
or:
(minDate.toEpochDay()..maxDate.toEpochDay()).forEach {
// ...
}
Iterate with day as date:
generateSequence(minDate) { it.plusDays(1) }.takeWhile { it < maxDate }.forEach {
// it ...
}
or:
var day = minDate;
while (day < maxDate) {
day = day.plusDays(1);
// ...
}