I am working on a story and am wondering how I can solve this problem similar to my current work at work, so a weekly streak is defined as one or more consecutive active weeks in which a user has worked out. So say you are given an array of active user days, we need find the length of the longest active weekly streak.
Here each element in the array represents an offset from an arbitrary Monday in the calendar (e.g. Thursday Feb 4th 2022).
The offsets are zero indexed.
Some helpful tips are:
Active day there is at least one exercise for the user during a calendar day
Active week is a calendar week, starting on a Monday, with at least one active day for a user
Weekly streak being One or more weeks (consecutive) active weeks in a user's workout history
The length of the streak is defined as the number of active weeks in the streak
Here is my code:
val str1 = arrayOf(9, 14, 365, 366, 367) //2
val str2 = arrayOf(2, 22, 29, 37, 43, 366, 391) //4
fun findLongestStreak(callDays: Array<Int>): Int{
}
What is the simplest way to write this function I have been stuck for a week now?
You could do it like this
val str1 = arrayOf(9, 14, 365, 366, 367) //2
val str2 = arrayOf(2, 22, 29, 37, 43, 366, 391) //4
fun toWeek(day: Int) = day / 7
fun activeWeeks(days: Array<Int>) = days.map(::toWeek).toSet().sorted()
// find runs of consecutive numbers in a list of integers,
// and return a list of those runs' lengths
fun getStreaks(nums: List<Int>): List<Int> =
if (nums.isEmpty()) emptyList()
// We're going to iterate over each adjacent pair of numbers, so we can check
// if the second number immediately follows the first, so we can add to
// the current streak.
// Since we're looking at the second number in each pair, the first in the list
// (we know there's at least one) is a special case - so we start the list
// we're building up with a streak of 1, representing that first number
else nums.zipWithNext().fold(mutableListOf(1)) { streaks, (prev, current) ->
streaks.apply {
// if the second number (the one we're checking) follows the first,
// add 1 to the most recent streak length in the list
if (current == prev + 1) streaks[lastIndex] += 1
// if they're not consecutive, we need to start a new streak for this number
else streaks.add(1)
}
}
fun findLongestStreak(callDays: Array<Int>): Int =
callDays.run(::activeWeeks)
.run(::getStreaks)
.run { maxOrNull() ?: 0 }
That's mostly explanation - basically I'm converting all the day numbers into their corresponding week numbers, using toSet() to remove duplicates and sorted to turn that into a List of ascending week numbers
Then the fold function starts with a streak of 1 for the first week number, then goes through the rest of them, comparing each to the previous week number. If it's consecutive, add 1 to the current streak. If it's not, start a new streak of length 1. (If you don't like the fold, you could just do a for loop with the same logic)
That way you end up with a list of streak lengths and you can just grab the longest one
The code is based on the idea that consecutive weeks minus their position index in the array lead to the same (meaningless) number, while with two non-consecutive weeks the two numbers will be different:
val str1 = arrayOf(9, 14, 365, 366, 367)
val str2 = arrayOf(2, 22, 29, 37, 43, 366, 391)
fun findLongestStreak(callDays: Array<Int>): Int {
return callDays
.map { it / 7 } // [1, 2, 52, 52, 52]
.toSet() // [1, 2, 52]
.mapIndexed { index, i -> i - index } // [1, 1, 50]
.groupBy { it } // {1=[1, 1], 50=[50]}
.maxOf { it.value.count() } // 2
}
println(findLongestStreak(str1)) // 2
println(findLongestStreak(str2)) // 4
You can use a Set that has a unique values for the workout weeks. Then filter the set with non-consecutive weeks (the idea is that for an arbitrary day, it should not belong to a week that is just equals to the previous day's week + 1);
Eventually, the size of the outcome is what you need (the active discrete weeks):
UPDATE:
Simplifying with toSet() instead of the for loop to be more linear:
fun findLongestStreak(callDays: Array<Int>) = callDays.map { it / 7 }.toSet()
.let { it.filter { day -> !it.contains(day - 1) } }.size
Original answer:
fun findLongestStreak(callDays: Array<Int>): Int {
val workoutWeeks = mutableSetOf<Int>() // all the workout weeks
for (day in callDays) workoutWeeks.add(day / 7)
return workoutWeeks.filter { !workoutWeeks.contains(it - 1) }.size
}
Notice that the day / 7 returns the week number offset similarly referring as they are offset to the base Monday.
Related
i have an list of object , in that object i have a value timestamp and it is in "Timestamp": "2021-12-16T07:30:13.950774575Z",this format. I have a requirement when i click on a textview named 60 mins , i should get the data for 60 mins from end time , that is if i have data between 4pm to 7pm , on on click i need data from 6pm to 7pm . That is need to subtract 1 hour and filter the data, i have tried but i didn't get proper solution, can anyone help me how can it be achieved . Please help me as i am a beginner .
I would use java.time and …
parse the String received from a button click (mocked, of course)
subtract precisely one hour from the value
use the result in order to filter from an example list
Here it is, all in a fun main, read the comments, please:
fun main(args: Array<String>) {
// your example String received on button click
val timestamp = "2021-12-16T07:30:13.950774575Z"
// example values to be filtered
val someObjects = listOf(
SomeObject(0, "2021-12-12T07:30:13.950774575Z"),
SomeObject(1, "2021-12-13T07:30:13.950774575Z"),
SomeObject(2, "2021-12-13T07:30:13.950774575Z"),
SomeObject(3, "2021-12-14T07:30:13.950774575Z"),
SomeObject(4, "2021-12-15T07:30:13.950774575Z"),
// here's the border
SomeObject(5, "2021-12-16T07:30:13.850774575Z"),
SomeObject(6, "2021-12-16T07:30:13.960774575Z"),
SomeObject(7, "2021-12-17T07:30:13.950774575Z"),
SomeObject(8, "2021-12-18T07:30:13.950774575Z")
)
// parse the timestamp captured on button click and subtract an hour
val filterByOdt = OffsetDateTime.parse(timestamp).minusHours(1)
// filter the list using the OffsetDateTime
val filteredObjects = someObjects.filter {
OffsetDateTime.parse(it.timestamp)
.isBefore(filterByOdt)
}
// print the ids of the results
println(filteredObjects.joinToString(", ") { "${it.id}" })
}
I used the following dummy class in the example.
data class SomeObject(val id: Long, val timestamp: String)
As desired, the filtered list only contains the 5 SomeObjects with a timestamp before timestamp:
0, 1, 2, 3, 4
Put all your data in a list and then filter the list for the elements within the last 60 minutes.
var now = currentTS();
var l = listOf(parseTS("2021-12-16T07:30:13.950774575Z"), parseTS(...),...);
var result = l.filter { now.inMinutes() - it.inMinutes() < 60 }
I don't know which sort of timestamp class you are using, so adjust currentTS, parseTS, inMinutes to your needs.
I'm building a chat platform, where I'm reading my list of messages from Local Room DB (ChatModel). I need to add date separators between these messages. I've to use multiple view holders and thus created a sealed class for differentiating items
sealed class ChatUiModel {
data class ChatItem(val message: ChatModel) : ChatUiModel()
data class DateSeparatorItem(val time: String) : ChatUiModel()
}
I require to convert the list with date separate items in between 2 models of the list, I'm not proficient with Collection functions in kotlin and confused between map/flatmap etc.
.observe(viewLifecycleOwner) { messages ->
messages.map {
// if item prev.date < item next.date
ChatUiModel.DateSeparatorItem(it.date.toReadableTime())
ChatUiModel.ChatItem(it)
}
chatAdapter.submitList(messages)
}
Reached to this
val items = mutableListOf<ChatUiModel>()
val data = messages.listIterator()
for (item in data) {
if (data.hasPrevious())
if (data.previous().time < item.time)
items.add(ChatUiModel.DateSeparatorItem(item.time))
items.add(ChatUiModel.ChatItem(item))
}
Timber.i("CHAT = $items")
An easy way to prepare the list can be:
messages
.groupBy { it.date }
.map { (date, chatModels) ->
listOf(DateSeparatorItem(date)) + chatModels.map { ChatItem(it) }
}
.flatten()
try it yourself
Here we first group all the messages by their data to get a Map<Long, List<ChatModel>. Then we map each entry of the map to a new list containing the DateSeparator and the ChatItems for that date. Finally, we flatten the entire list to get the desired List<ChatUiModel>.
In the code that I linked, I have used Long for the date. If you have a String you can easily interconvert them using java.time APIs.
If your messages list is not sorted initially, add a sortedBy function before groupBy to sort it first.
(this ended up long but I thought you'd like an explanation of what's going on - you can just skip to the end for the solutions if you want)
Ok, so this is a little tricky if you're not familiar with all the utility functions and general functional manipulation - what you're basically doing is transforming incoming messages into ChatItems, but you also want to compare each message to the previous one, and output a DateSeparatorItem first where necessary, right?
A straight map isn't going to work - that just transforms each item into another item (it's mapping one value to another), and sometimes you want to transform one item into two (a date item and a chat item).
You could map each message item into a list, and make that contain either a chat item, or a date+chat. So that would give you a list of lists, which you could then flatten so you just get all those items in order, in a single list. That's basically what flatmap does!
So now you need to be able to compare multiple messages, so you can check the dates. Kotlin has this windowed function that acts like a sliding view across your collection, so it can transform [1, 2, 3, 4] into [[1, 2], [2, 3], [3, 4]], and then you can work on those groups. There's a more convenient zipWithNext function that only produces Pairs instead of arbitrarily sized Lists - i.e. [(1, 2), (2, 3), (3, 4)], but windowed has a useful option - partialWindows allows that window to keep moving to the end of the list, even as it runs out of items to fill the full window:
listOf(1, 2, 3, 4).windowed(size=3, partialWindows=true).run(::println)
>> [[1, 2, 3], [2, 3, 4], [3, 4], [4]]
If we do this for a window of size 2, we get every original message, and also the one following it if there is one (zipWithNext will stop when it runs out of complete pairs):
listOf(1, 2, 3, 4).windowed(size=2, partialWindows=true).run(::println)
>> [[1, 2], [2, 3], [3, 4], [4]]
We can use this!
Your logic right now is taking a message and comparing it to the previous one to see if a date needs inserting before the chat item - I'd suggest flipping that around, and inserting a date after the current item by checking the next item's timestamp. That's because windowed is giving you each item along with the next one, so you don't get to look at the previous one.
We're working with a list here, and we need to compare the first item to the second one (checking if there even is one), but we can be a little bit cheeky and just compare list.first() with list.last(). We know there's gonna be either one or two items - and if there's only one item in the list (i.e. it's the last message) then we're comparing it with itself, and since we're only adding the date item if the timestamps are different... well they won't be if it's the same item! So there won't be any rogue date items added at the end. Probably worth documenting the code if you do that since it might not be clear - you can write some more explicit logic if you want.
Here's a few ways to do the final thing:
Kotlin Playground example
data class Message(val text: String, val time: Int)
val messages = listOf(
Message("hey", 1),
Message("u up", 1),
Message("lol", 3),
Message("wow", 10)
)
fun withMutableList() {
messages.windowed(size=2, partialWindows=true)
// or map followed by flatten()
.flatMap { items ->
val current = items.first()
val next = items.last()
// creating a mutable list with the chat item, optionally adding a date
mutableListOf<ChatUiModel>(ChatItem(current)).apply {
if (next.time > current.time) add(DateItem(next.time))
}
}
.forEach(::println)
}
fun withNulls() {
messages.windowed(size=2, partialWindows=true)
.flatMap { items ->
val current = items.first()
val next = items.last()
// either adding a date or a null, nulls get removed later
listOf(
ChatItem(current),
if (next.time > current.time) DateItem(next.time) else null
)
}
.filterNotNull()
.forEach(::println)
}
fun withSequence() {
sequence {
messages.windowed(size=2, partialWindows=true)
.forEach { items ->
val current = items.first()
val next = items.last()
// just yielding a stream of items, nice and neat!
yield(ChatItem(current))
if (next.time > current.time) yield(DateItem(next.time))
}
}.forEach(::println)
}
all giving this output:
ChatItem(message=Message(text=hey, time=1))
ChatItem(message=Message(text=u up, time=1))
DateItem(time=3)
ChatItem(message=Message(text=lol, time=3))
DateItem(time=10)
ChatItem(message=Message(text=wow, time=10))
I have an app on the PlayStore and I am building a feature where the user will not see ads more than a specific number in one day.
I am thinking about comparing the current date and time to the previously saved one but haven't find a proper way to do that.
How can I compare date and time to know if 24 hours have passed or not?
Some posts that I found but not helpful:
medium.com
stackoverflow
stackoverflow
tl;dr
[This Answer uses Java syntax. You’ll have to translate to Kotlin syntax.]
if
(
Duration // Represents elapsed time on the scale of hours-minutes-seconds.
.between( // Calculates elapsed time between two points in time.
Instant.parse( "2021-03-23T15:30:57.013678Z" ) , // Last moment when an ad was show.
Instant.now() // Current moment.
) // Returns a `Duration` object.
.toHours() // Extract total number of whole hours from the `Duration` object.
>= 24L // Test if equals-to or greater-than 24 hours.
)
{ show ad }
java.time
You asked:
… know if 24 hours have passed or not?
Use the modern java.time classes defined in JSR 310. The java.time classes are built into Android 26 and later. Most of the functionality is available in earlier Android using the latest tooling’s “API desugaring“.
Instant adShown = Instant.parse( "2021-03-23T15:30:57.013678Z" ) ;
Instant now = Instant.now() ;
Duration d = Duration.between( adShown , now ) ;
long hoursSinceAdShown = d.toHours() ;
if( hoursSinceAdShown >= 24L ) { … show ad }
Record your next ad-showing as text in standard ISO 8601 format.
String output = Instant.now().toString() ;
2021-03-23T15:30:57.013678Z
Your Question asked for two different things:
Once per day
Every 24 hours
The first involves a calendar, dates, and a time zone. The second does not. I showed you code for the second.
You can use a scheduled executor service to trigger from a background thread the next showing of an ad at a specific moment. Search Stack Overflow to learn more as this has been covered many times already.
Use this code to check the current date, Yesterday or Particulardate. Pass Epoch time to this method
// input format (we get a value as Epoch)
private val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private val outputFormat = SimpleDateFormat("MMM dd")
// have to pass the time value as Epoch time.
private fun calculateDateMonth(time: String): String {
var returnValue = ""
val dateTime = DateTime((time.toLong()) * 1000L)
val inputTime = inputFormat.parse(dateTime.toString())
val convertDateMonth = outputFormat.format(inputTime!!)
val timeInMilliseconds = outputFormat.parse(convertDateMonth)!!
val mTime: Calendar = Calendar.getInstance()
mTime.setTimeInMillis(timeInMilliseconds.time)
val now = Calendar.getInstance()
returnValue = when {
now[Calendar.DATE] == mTime[Calendar.DATE] // check isToday
now[Calendar.DATE] - mTime[Calendar.DATE] == 1 // check Yesterday
else -> convertDateMonth // Month and Date
}
return returnValue
}
So I am fairly new to Kotlin and I need to generate specific numbers from a for loop of 1 to 13.
For the first output I need only odd numbers
For the second output I need numbers 2, 5, 8, 11, 14, 19 and 20 from a for loop of 0 to 20
For starters I can print an entire list using:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
for (i in 1..13){
println(i)
}
}
}
But that's it. What do I need to print the other required outputs?
Once you know how to write a for loop that prints every number, the question becomes how to identify a number that you "should" print from a number that you should not.
Your first sequence is all odd numbers, so #DipankarBaghel's answer covers that. Your second sequence seems to be all numbers for which the remainder when dividing by 3 is 2. (Except 19; did you mean 17 for that one?)
You can use the same operator in this case, but instead of checking for 0 (or for != 0) you can check that the remainder is 2:
for (i in 0..20) {
if (i % 3 == 2) {
println(i)
}
}
The key concept here is that of %, the remainder operator (sometimes called the modulo operator). The result of x % y will be the remainder when x is divided by y. Odd numbers have a remainder of 1 when divided by 2, so i % 2 == 1 will be true only for (positive) odd numbers.
To check even you need to do i%2==0 and for odd just check i%2!=0.
for (i in 1..13){
if(i%2!=0){
println("odd number "+i);
}
if(i%2==0){
println("even number "+i);
}
}
Hope this will help you.
To generate Odd numbers:
for (i in 1..13) {
if(i % 2 == 1 ){
println(i + ", ");
}
}
To Generate 2, 5, 8, 11, 14, 19 and 20:
for (i in 0..20) {
if (i % 3 == 2) {
println(i + ", ");
}
}
I have a list of 30 random numbers that correspond to 1 of 8 colours, and I need to iterate over the 8 colors(or 30 numbers) and find the number of times each colour occurs. I need to do this using lambdas and functional programming, so no traditional for loops.
val iterator = colours.toList().iterator()
iterator.forEach{
println("$it count: " + (numbers
.map{a -> colours[a]}
.count{it == ("$it")}))
}
The problem currently is my output for count is just 50, not the specific number of times a colour occurs.
If I do it like this:
println("Red count:" + (numbers
.map{a -> colours[a]}
.count{it == ("red")}))
it outputs the correct number, but not with the loop.
What it ouputs:
green count: 50
red count: 50
what it should output (for example)
green count:9
red count:3
Thanks in advance
Add a named parameter to your forEach loop. The implicit name "it" is getting shadowed by the count function.
val iterator = colours.toList().iterator()
iterator.forEach { colour ->
println("$colour count: " + (numbers
.map{a -> colours[a]}
.count{it == ("$colour")}))
}
You don't really need to do a nested iteration here. Currently you're operating at O(n^2) since you have to traverse the list once for every element. Since you know you're working with a small number of potential values, you could instead just group them by value and then map the values to the size of the resulting lists, i.e.
val colourNames = listOf("red", "green", "blue", "yellow", "orange", "indigo", "violet", "black")
// Generates 30 random numbers between 0 and 8 (exclusive)
val randomColours = (0 until 30).map { (0 until colourNames.size).random() }
val result = randomColours
.groupBy { color -> colourNames[color] } // outputs a Map<String, List<Int>>
.mapValues { (color, colorCountList) -> colorCountList.size } // Map<String, Int>
println(result) // {yellow=4, orange=4, red=5, indigo=3, blue=8, green=2, violet=2, black=2}