How can I use elegant kotlin techniques to solve this problem? (FP) - kotlin

I have a kotlin problem, can you come up with a elegant way of solving it?
so effectively I have a list of Journeys which could be by car or not, I want to find where there are consecutive car journeys in a list; i.e. 2 or more cars in a row in the list. Then from these chains of consecutive cars I wan to sum the mileage per chain and print out the maximum. This is the data class
data class Journey(
val isCar: Boolean = false,
val mileage: Int,
)
Here is an example list of journeys. So in this example the correct answer should be 80, because there are two consecutive entries with 40+40 as the mileage. That beats the only other consecutive entry of 10+20=30. The rest are single car journeys and non car journeys so are ignored.
listOf(
Journey(true, 10),
Journey(true, 20),
Journey(false, 105),
Journey(true, 1046),
Journey(false, 130),
Journey(true, 40),
Journey(true, 40),
)
My solution is below, however it's very ugly and non-functional. Can you solve this in an elegant way using modern functional techniques without mutable Lists? Thanks
val journeys = listOf(
Journey(true, 10),
Journey(true, 20),
Journey(false, 105),
Journey(true, 1046),
Journey(false, 130),
Journey(true, 40),
Journey(true, 40),
)
val cars = journeys.filter { it.isCar }
val journeysByIndex = cars.map { journeys.indexOf(it) to it }
val consecutiveJourneys = mutableListOf<MutableList<Journey>>()
journeysByIndex.forEach {
if (consecutiveJourneys.any { cj1 -> cj1.any { cj2 -> cj2 == it.second } }) {
return#forEach
}
val journeyList = mutableListOf(it.second)
var index = it.first
var nextJourneyExists = true
while (nextJourneyExists) {
var journey: Journey? = null
if ((index + 1) < journeys.size && journeys[index + 1].isCar) {
journey = journeys[index+1]
}
nextJourneyExists = if (journey!= null) {
journeyList.add(journey)
true
} else {
false
}
index += 1
}
consecutiveJourneys.add(journeyList)
}
var maxAmountForConsecutive = consecutiveJourneys.filter {it.size > 1 }.map{ it.sumOf {cs -> cs.mileage}}.max()
println("max1: " + maxAmountForConsecutive)

Besides the grouping method that Tenfour04 suggested in the comment, which I personally find a bit overkill for a task like this (but it is useful in other scenarios), you could also just simply count.
We can create a simple extension function that counts each step, then resets once it hits a isCar:false:
fun Collection<Journey>.calculateMaxByCarConsecutive(): Int {
var max = 0
var currentMileageSum = 0
var count = 0
forEach { current ->
if (current.isCar) {
//if the current journey is in a car, add its mileage to our current count
currentMileageSum += current.mileage
//increase count that tells us how many journeys have been consecutive
count++
//if we have more than 1 journey creating the currentMileageSum
//and
//currentMileageSum is greater than max,
// then the currentMileageSum becomes the new max
if (count > 1) max = maxOf(currentMileageSum, max)
} else {
//reset the counters if this record is not by car
count = 0
currentMileageSum = 0
}
}
return max
}
Then we can call it like this:
val journeys = listOf(
Journey(true, 10),
Journey(true, 20),
Journey(true, 40),
Journey(false, 105),
Journey(true, 10423423),
Journey(false, 130),
Journey(true, 40),
Journey(true, 40),
Journey(true, 10),
)
val maxJourneyByCar = journeys.calculateMaxByCarConsecutive()
println(maxJourneyByCar) //this will print 90
Not sure if this is more elegant that your solution or than using fold, but I do think it is simpler.

Related

Kotlin: create a list from another list

I want to create a list using Kotlin that contains items of another list, based on endDate equals to startDate and .. etc
Example:
listOf(
{id1, startDate=1, endDate=3},
{id3, startDate=5, endDate=6},
{id2, startDate=3, endDate=5},
{id4, startDate=10, endDate=12},
{id5, startDate=12, endDate=13},
{id6, startDate=13, endDate=16})
result listOf[{id1}, {id2}, {id3}], [{id4}, {id5}, {id6}] // these are two items
With the given dataset, this problem looks innocent at a first glance, but may grow to a more complex problem quickly. Imagine a dataset that has the potential of multiple, possible results. Should longest possible chains be preferred, or a result with balanced chain size?
A naive implementation may be like this (written inside a Kotest).
data class ListItem(
val id: String,
val startDate: Int,
val endDate: Int
)
given("another StackOverflow issue") {
val coll = listOf(
ListItem("id1", startDate = 1, endDate = 3),
ListItem("id3", startDate = 5, endDate = 6),
ListItem("id2", startDate = 3, endDate = 5),
ListItem("id4", startDate = 10, endDate = 12),
ListItem("id5", startDate = 12, endDate = 13),
ListItem("id6", startDate = 13, endDate = 16)
)
`when`("linking chain") {
/** final result ends up here */
val chains: MutableList<MutableList<ListItem>> = mutableListOf()
/** populate dequeue with items ordered by startDate */
val arrayDeque = ArrayDeque(coll.sortedBy { it.startDate })
/** loop is iterated at least once, hence do/while */
do {
/** add a new chain */
chains.add(mutableListOf())
/** get first element for chain */
var currentItem: ListItem = arrayDeque.removeFirst()
/** add first element to current chain */
chains.last().add(currentItem)
/** add items to current chain until chain is broken */
while (arrayDeque.any { it.startDate == currentItem.endDate }) {
/** get next element to add to chain and remove it from dequeue */
currentItem = arrayDeque
.first { it.startDate == currentItem.endDate }
.also { arrayDeque.remove(it) }
chains.last().add(currentItem)
}
} while (arrayDeque.any())
then("result should be as expected") {
chains.size shouldBe 2
chains.first().size shouldBe 3
chains.last().size shouldBe 3
chains.flatMap { it.map { innerItem -> innerItem.id } } shouldBe listOf(
"id1",
"id2",
"id3",
"id4",
"id5",
"id6",
)
}
}
}

how to update a list by an other list of the same type on kotlin

I have 2 list of Skill object.
AllSkills represent the full list of skills that can be found in the app.
UserSkills represent the skills that the user has on his account.
What I want to do is to replace any skill in AllSkill that exist in UserSkill (having the same id) by the skill of UserSkill,
But I don't understand how to do it, can someone help me pls ?
Exemple:
val allSkills = listOf<Skill>(Skill(id = 0, note = null), Skill(id = 1, note = null)
val userSkills = listOf<Skill>(Skill(id = 1, note = 2)
then the result I want is :
val result = listOf<Skill>(Skill(id = 0, note=null, Skill(id = 1, note = 2)
-> The skill contained in userSkill (id = 1) as the note of the userSkill
You can transform the user skills into a Map<Int, Skill> by associating them with the ID and proceed with replacing allSkills if it exists in such map.
Haven't tested it but it could look like this:
val allSkills = listOf<Skill>(Skill(id = 0, note = null), Skill(id =0, note = null)
val userSkills = listOf<Skill>(Skill(id = 1, note = 2)
val userSkillsMap = userSkills.associateBy { it.id }
// userSkillsMap[skill.id] returns the skill if it exists
// userSkillsMap[skill.id] returns null if it doesnt exist
// With the ?: (elvis operator) we default back to the present skill
val allSkillsUpdated = allSkills.map { skill -> userSkillsMap[skill.id] ?: skill }
data class Skill(
val id: Int,
val note: Int?
)
val allSkills = listOf(
Skill(id = 0, note = null),
Skill(id = 1, note = null)
)
val userSkills = listOf(
Skill(id = 1, note = 2)
)
val result = allSkills
.map { allSkill ->
userSkills
.firstOrNull { userSkill -> userSkill.id == allSkill.id } ?: allSkill
}
Note that for large skill lists #SomeRandomITboy's solution will most probably be faster.

Merge and order two streams using Kotlin flow

I have two streams where each stream has a different set of values and a different amount:
runBlocking {
val flowA = flow {
mutableListOf<Int>(0, 4, 9).forEach {
emit(it)
}
}
val flowB = flow {
mutableListOf<Int>(1, 2, 3, 5, 6, 7, 8).forEach {
emit(it)
}
}
merge(flowA, flowB).collect{
Log.i(TAG, it.toString())
}
}
Is it possible to use Kotlin's Flow to merge these two streams so that the result is sorted? So the collected values should end up being:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
The values in each stream is already sorted. I just need to merge them. One very important thing however. I don't want to sort AFTER all the values have been collected. The sorting must be done as each value is emitted. My sample above is over simplified. In the real app, the source for each flow contains large arrays for each item. Waiting for all the values to be collected and then sorting is unacceptable as this would require a large amount of memory. But the basic concept for simple integer values should work for more complex data types as well.
Maybe the filter operator is what I need but that isn't clear as I have little experience with flows.
Disclaimer: This is the first time I've used Flow.
Even though the streams are "already sorted", it seems you cannot control the timing the elements will arrive from the two streams. So, you will only be able to get an ordered list by collecting all the elements, then sorting them.
This worked for me:
val sortedResults = flowA
.onCompletion { emitAll(flowB) }
.toCollection(mutableListOf())
.sorted()
println(sortedResults)
Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
You could use the combine operator to get value from two or more flows and then a flatMapLatest operator like this:
val desiredFlow = combine(getFlowA(),getFlowB()) { a, b ->
val arr = IntArray(a.size + b.size)
var i = 0
var j = 0
var k = 0
while (i < a.size && j < b.size)
arr[k++] = if (a[i] < b[j]) a[i++] else b[j++]
while (i < a.size)
arr[k++] = a[i++]
while (j < b.size)
arr[k++] = b[j++]
arr
}.flatMapLatest { result ->
flow {
emit(result.toMutableList())
}
}
fun getFlowA(): Flow<MutableList<Int>> {
return flow {
emit(mutableListOf<Int>(0,4,9))
}
}
fun getFlowB(): Flow<MutableList<Int>> {
return flow {
emit(mutableListOf(1,2,3,4,5,6,7,8))
}
}
I'm from the Android dev world and not expert with Flows so kindly pardon me if isn't what you expected, but this produces the final output as:
[0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9]
After going through the question, I have two ideas either using a flatten merge or using a delay.
The first idea looks something like this.
runBlocking {
val flowA = flow {
mutableListOf<Int>(0, 4, 9).forEach {
emit(it)
}
}
val flowB = flow {
mutableListOf<Int>(1, 2, 3, 5, 6, 7, 8).forEach {
emit(it)
}
}
val newList = mutableListOf<Int>()
val a = flowOf(flowA, flowB).flattenMerge().collect { value ->
when {
newList.isEmpty() -> newList.add(value)
newList.last() <= value -> newList.add(value)
newList.last() > value -> {
//sorting values as they arrive
val i = newList.lastIndex
newList.add(value)
val newValue = newList[i]
newList[i] = newList[i + 1]
newList[i + 1] = newValue
}
}
}
Log.i(TAG, newList.toString())
}
In the second one, add appropriate delays to your first 2 flows.
PS:-
Android Studio gives a warning while using flattenMerge.
This declaration is in a preview state and can be changed in a backwards-incompatible manner with a best-effort migration. Its usage should be marked with '#kotlinx.coroutines.FlowPreview' or '#OptIn(kotlinx.coroutines.FlowPreview::class)' if you accept the drawback of relying on preview API
I don't think you can do this using the built-in flow operators, but you should certainly be able to implement your own. You can use channelFlow for that purpose. This is a versatile way to build a flow that gives us a coroutine scope to work in, and lets us emit items by sending to a channel.
fun <T> mergeOrdered(flowA: Flow<T>, flowB: Flow<T>) = channelFlow {
val channelA = flowA.produceIn(this)
val channelB = flowB.produceIn(this)
var a = channelA.receive()
var b = channelB.receive()
while (isActive) {
if (a < b) {
send(a)
a = channelA.receive()
} else {
send(b)
b = channelB.receive()
}
}
}
This simple example doesn't handle what happens when flowA and flowB run out of elements, but that should be easy enough to add.

Kotlin - How to check if a char is present in a matrix?

Only for context: I am trying to implement Playfair Cipher. It would be really helpful if you take a look at Playfair Cipher to understand my problem.
This program is just for some background:
fun main(){
println("Enter the message:")
var message:String = readLine()!!.toUpperCase()
println("Enter the key:")
var key:String = readLine()!!.toUpperCase()
var cipTable = Array(5){ Array(5){'X'}}
var j=0; //to iterate througm my key
for(innerArray in cipTable){
for(i in innerArray.indices){
if(key[j++] !in cipTable)
innerArray[i]+=key[j]
if(j==key.length) break
}
}
}
My main issue is with this part:
for(innerArray in cipTable){
for(i in innerArray.indices){
if(key[j++] !in cipTable)
I wanted to check if the key that I am going to insert in the matrix is already present in it or not. I also cannot use innerArray instead of cipTable as it would only check for char in the same row. Is there any way I can check if a char is present or not in the entire matrix?
For eg.:
fun main(){
var result = arrayOf(
intArrayOf(3, 2, 4),
intArrayOf(6, 7, 9),
intArrayOf(12, 11, 23)
)
//To check if 2 is present in the entire matrix/table
if(result.any { 2 !in it}) println("not present") else print("present")
}
Can you tell me what is wrong in this code because the output is not expected. Also is there any way I can use forEach for the same.
If I understand correctly, you want this for loop to result in a true or false based on whether any inner array has the same sequence and number of chars as the key String.
First of all, the inner array should be a CharArray instead of an Array<Char>, to avoid boxing.
val cipTable = Array(5) { CharArray(5) { 'X' } }
Then you can use all and contentEquals to check if any of the inner CharArrays are a match for the key.
val charArrayKey = key.toCharArray()
val isKeyInTable = cipTable.any { it.contentEquals(charArrayKey) }
If you want to skip the step of converting the key to a CharArray, you can manually check it like this:
val isKeyInTable =
cipTable.any { it.size == key.length && it.withIndex().all { (i, c) -> c == key[i] } }
I guess one way is to use extensions.
fun Array<IntArray>.has(x:Int):Boolean{
for(innerArray in this){
if(x in innerArray)
return true
}
return false
}
fun main(){
var result = arrayOf(
intArrayOf(3, 2, 4),
intArrayOf(6, 7, 9),
intArrayOf(12, 11, 23)
)
//To check if 43 or 4 is present in the entire matrix/table
if(result.has(43)) println("present") else println("not present")
if(result.has(4)) println("present") else println("not present")
}

What is the cleanest way to get the sum of numbers in a collection/list in Dart?

I don't like using an indexed array for no reason other than I think it looks ugly. Is there a clean way to sum with an anonymous function? Is it possible to do it without using any outside variables?
Dart iterables now have a reduce function (https://code.google.com/p/dart/issues/detail?id=1649), so you can do a sum pithily without defining your own fold function:
var sum = [1, 2, 3].reduce((a, b) => a + b);
int sum = [1, 2, 3].fold(0, (previous, current) => previous + current);
or with shorter variable names to make it take up less room:
int sum = [1, 2, 3].fold(0, (p, c) => p + c);
This is a very old question but
In 2022 there is actually a built-in package.
Just import
import 'package:collection/collection.dart';
and call the .sum extension method on the Iterable.
FULL EXAMPLE
import 'package:collection/collection.dart';
void main() {
final list = [1, 2, 3, 4];
final sum = list.sum;
print(sum); // prints 10
}
If the list is empty, .sum returns 0.
You might also be interested in list.average...
I still think this is cleaner and easier to understand for this particular problem.
num sum = 0;
[1, 2, 3].forEach((num e){sum += e;});
print(sum);
or
num sum = 0;
for (num e in [1,2,3]) {
sum += e;
}
There is not a clean way to do it using the core libraries as they are now, but if you roll your own foldLeft then there is
main() {
var sum = foldLeft([1,2,3], 0, (val, entry) => val + entry);
print(sum);
}
Dynamic foldLeft(Collection collection, Dynamic val, func) {
collection.forEach((entry) => val = func(val, entry));
return val;
}
I talked to the Dart team about adding foldLeft to the core collections and I hope it will be there soon.
Starting with Dart 2.6 you can use extensions to define a utility method on the List. This works for numbers (example 1) but also for generic objects (example 2).
extension ListUtils<T> on List<T> {
num sumBy(num f(T element)) {
num sum = 0;
for(var item in this) {
sum += f(item);
}
return sum;
}
}
Example 1 (sum all the numbers in the list):
var numbers = [1, 2, 3];
var sum = numbers.sumBy((number) => number);
Example 2 (sum all the Point.x fields):
var points = [Point(1, 2), Point(3, 4)];
var sum = points.sumBy((point) => point.x);
I'd just like to add some small detail to #tmaihoff's answer (about using the collection.dart package):
The sum getter he talks about only works for iterables of num values, like List<int> or Set<double>.
If you have a list of other object types that represent values (like Money, Decimal, Rational, or any others) you must map it to numbers. For example, to count the number of chars in a list of strings you can do:
// Returns 15.
['a', 'ab', 'abc', 'abcd', 'abcde'].map((e) => e.length).sum;
As of 2022, another way of doing it, is using the sumBy() method of the fast_immutable_collections package:
// Returns 15.
['a', 'ab', 'abc', 'abcd', 'abcde'].sumBy((e) => e.length), 15);
Note: I'm the package author.
I suggest you to create this function in any common utility file.
T sum<T extends num>(T lhs, T rhs) => lhs + rhs;
int, double, float extends num class so you can use that function to sum any numbers.
e.g.,
List<int> a = [1,2,3];
int result = a.reduce(sum);
print(result); // result will be 6
Herewith sharing my Approach:
void main() {
int value = sumTwo([1, 4, 3, 43]);
print(value);
}
int sumTwo(List < int > numbers) {
int sum = 0;
for (var i in numbers) {
sum = sum + i;
}
return sum;
}
If when using fold gives a double TypeError, you can use reduce:
var sum = [0.0, 4.5, 6.9].reduce((a, b) => a + b);
If you are planning on doing a number of mathematical operations on your list, it may be helpful to create another list type that includes .sum() and other operations by extending ListBase. Parts of this are inspired by this response with performance tweaks from this response.
import 'dart:collection';
import 'dart:core';
class Vector<num> extends ListBase<num> {
List<num> _list;
Vector() : _list = new List<num>();
Vector.fromList(List<num> lst): _list = lst;
void set length(int l) {
this._list.length=l;
}
int get length => _list.length;
num operator [](int index) => _list[index];
void operator []=(int index, num value) {
_list[index]=value;
}
// Though not strictly necessary, for performance reasons
// you should implement add and addAll.
void add(num value) => _list.add(value);
void addAll(Iterable<num> all) => _list.addAll(all);
num sum() => _list.fold(0.0, (a, b) => a + b) as num;
/// add additional vector functions here like min, max, mean, factorial, normalize etc
}
And use it like so:
Vector vec1 = Vector();
vec1.add(1);
print(vec1); // => [1]
vec1.addAll([2,3,4,5]);
print(vec1); // => [1,2,3,4,5]
print(vec1.sum().toString()); // => 15
Vector vec = Vector.fromList([1.0,2.0,3.0,4.0,5.0]); // works for double too.
print(vec.sum().toString()); // => 15
A solution that has worked cleanly for me is:
var total => [1,2,3,4].fold(0, (e, t) => e + t); // result 10
Different ways to find the sum of all dart list elements,
Method 1: Using a loop :
This is the most commonly used method. Iterate through the list using a loop and add all elements of the list to a final sum variable. We are using one for loop here :
main(List<String> args) {
var sum = 0;
var given_list = [1, 2, 3, 4, 5];
for (var i = 0; i < given_list.length; i++) {
sum += given_list[i];
}
print("Sum : ${sum}");
}
Method 2: Using forEach :
forEach is another way to iterate through a list. We can also use this method to find out the total sum of all values in a dart list. It is similar to the above method. The only difference is that we don’t have to initialize another variable i and list.length is not required.
main(List<String> args) {
var sum = 0;
var given_list = [1, 2, 3, 4, 5];
given_list.forEach((e) => sum += e);
print("Sum : ${sum}");
}
Method 3: Using reduce :
reduce method combines all elements of a list iteratively to one single value using a given function. We can use this method to find out the sum of all elements as like below :
main(List<String> args) {
var given_list = [1, 2, 3, 4, 5];
var sum = given_list.reduce((value, element) => value + element);
print("Sum : ${sum}");
}
Method 4: Using fold :
fold() is similar to reduce. It combines all elements of a list iteratively to one single value using a function. It takes one initial value and calculates the final value based on the previous value.
main(List<String> args) {
var sum = 0;
var given_list = [1,2,3,4,5];
sum = given_list.fold(0, (previous, current) => previous + current);
print("Sum : ${sum}");
}
for more details:https://www.codevscolor.com/dart-find-sum-list-elements
extension DoubleArithmeticExtensions on Iterable<double> {
double get sum => length == 0 ? 0 : reduce((a, b) => a + b);
}
extension IntArithmeticExtensions on Iterable<int> {
int get sum => length == 0 ? 0 : reduce((a, b) => a + b);
}
Usage:
final actual = lineChart.data.lineBarsData[0].spots.map((s) => s.x).sum;