I have a list of custom objects. I would like to retrieve any 2 that meet 2 different conditions.
E.g.
Assume
class Person {
val age: Int
//etc
}
And I have a List<Person>. I would like to get 2 Person that have age == 21 or age == 31
I know I can do a loop for that and break using some flag once I find both, but I was wondering if there was some more Kotlin idiomatic way for this instead of:
var p1: Person?
var p2: Person?
list.forEach{
if(it.age == 21) {
p1 = it
}
else if(it.age == 31) {
p2 = it
}
if(p1 != null && p2 != null) break
}
I can only think of 2 possible solutions:
An inefficient but simple way (2 partial iterations)
val list = listOf(Person(21), Person(23), Person(31), Person(20))
var p1: Person? = list.find{ it.age == 21 }
var p2: Person? = list.find{ it.age == 31 }
This will iterate twice, but will stop the iteration as soon as a result is found.
Based on your own solution, only iterates once
val list = listOf(Person(21), Person(23), Person(31), Person(20))
var p1: Person? = null
var p2: Person? = null
//forEach is a method, not an actual loop, you can't break out of it, this is a bypass (you can also enclose it into a fun and return from there to act as a break)
run loop#{
list.forEach {
when {
p1 == null && it.age == 21 -> p1 = it
p2 == null && it.age == 31 -> p2 = it
}
if (p1 != null && p2 != null) return#loop
}
}
The p1 == null check will make is so that we don't reasign the same value multiple times, thus getting the first result. It is better even if we aren't necessarily looking for the first result, since it won't make multiple assignments.
Ps: both solutions will leave you with nullable p1 and p2, you must remember to deal with that accordingly afterwards.
No, you are trying to do quite a specific operation, so I don't think there is a built-in function and you will need to implement it yourself. Since you want to only iterate the list once, I think it would be best to:
Implement it with traditional list iteration
Wrap it up in a generic extension function for idiomatic Kotlin use
data class Person(val age: Int)
fun <T> Iterable<T>.findFirstTwo(
predicateA: (T) -> Boolean,
predicateB: (T) -> Boolean
): Pair<T, T> {
var first: T? = null
var second: T? = null
val iterator = iterator()
var remainingPredicate: (T) -> Boolean = { false }
while (first == null && iterator.hasNext()) {
val item = iterator.next()
when {
predicateA(item) -> {
first = item
remainingPredicate = predicateB
}
predicateB(item) -> {
first = item
remainingPredicate = predicateA
}
}
}
while (second == null && iterator.hasNext()) {
val item = iterator.next()
if (remainingPredicate(item)) {
second = item
}
}
require(first != null && second != null) { "Input does not satisfy predicates" }
return Pair(first, second)
}
fun main() {
val people = (1..20).map { Person(it) }
val (seven, twelve) = people.findFirstTwo({ it.age == 12 }, { it.age == 7 })
val (one, nineteen) = people.findFirstTwo({ it.age == 1 }, { it.age == 19 })
val error = try {
people.findFirstTwo({ it.age == 100 }, { it.age == 3 })
} catch (e: Exception) {
e.message
}
println(one)
println(seven)
println(twelve)
println(nineteen)
print(error)
}
Output:
Person(age=1)
Person(age=7)
Person(age=12)
Person(age=19)
Input does not satisfy predicates
As a lot of people mentioned that find will probably be the best solution for this kind of problem but we will have to use it twice, once for 21 and other time for 31.
even though find will return a single object: <T> Iterable<T>.find(predicate: (T) -> Boolean): T?
it doesn't mean we can't find another one in the same iteration.
for example:
var ageParam = -1
var p1: Person? = null
var p2: Person? = list.find { person ->
p1?.let {
//p1 - the first person found, now we will iterate until age == ageParam(21/31)
person.age == ageParam
} ?: run {
//p1 - the first person hasn't found yet.
if (person.age == 21) {
p1 = person // saving person
ageParam = 31 // setting next condition to test
} else if (person.age == 31) {
p1 = person
ageParam = 21
}
false // it's false in order to keep iterate after P1 found
}
}
Your solution is close to optimal. But it doesn't take first objects meeting criteria. For listOf(Person(id=1, age=21), Person(id=2, age=21), Person(id=3, age=31)) it will return p1 = Person(id=2, age=21).
To fix this you need to have additional comparsions with null in your conditional expressions (after implementing this, your second if-statement could be merged into branches of the first one to avoid repeating checks). Also, you can't use break inside forEach - it should be substituted with simple loop.
All together:
var p1: Person? = null
var p2: Person? = null
for (it in list) {
if (p1 == null && it.age == 21) {
p1 = it
if (p2 != null) break
} else if (p2 == null && it.age == 31) {
p2 = it
if (p1 != null) break
}
}
To generalize this you may create extension function:
fun <T> Iterable<T>.takeFirstTwo(predicate1: (T) -> Boolean, predicate2: (T) -> Boolean): Pair<T?, T?> {
var p1: T? = null
var p2: T? = null
for (it in this) {
if (p1 == null && predicate1(it)) {
p1 = it
if (p2 != null) break
} else if (p2 == null && predicate2(it)) {
p2 = it
if (p1 != null) break
}
}
return p1 to p2
}
//Usage:
val is21 = { it: Person -> it.age == 21}
val is31 = { it: Person -> it.age == 31}
val (p1, p2) = list.takeFirstTwo(is21, is31)
You can use the filter keyword to filter any collection
val matches = list.filter { if (it.age == 21 || it.age == 31) }
matches will be a list where all Person objects have an age of 21 or 31.
I do not think there is such a method in List, the closest statement would be get first match using list.first{it.age == 21 || it.age == 31 }, which will get the first item matching given predicate then breaks the loop, may be you can write your own extension to filter first n numbers
fun <T> Iterable<T>.firstN(n: Int, predicate: (T) -> Boolean): List<T> {
val output = ArrayList<T>()
var count = 0
for (element in this){
if(count == n) break
if (predicate(element)){
count++
output.add(element)
}
}
return output
}
You can do below to get first two elements
with(list.firstN(2){ it.age == 21 || it.age == 31}){
if(size == 2){
val (p1, p2) = this
}
}
I don't understand the loop in the following content.
There is a sequence of integer numbers (which ends with the number 0). Find the largest element of this sequence.
The number 0 itself is not included in the sequence. It serves only as a sign that there are no more numbers.
I tried various methods, but they did not solve the problem.
The way to exit the loop at 0 is to use
While(i ! = 0){}
Here is the code we created.
fun main(args: Array<String>) {
val scanner = Scanner(System.`in`)
var count = scanner.nextInt()
do {
var input = scanner.nextInt()
while(input != 0 || count != 0) {
if (count < input){
count = input
}
}
}while(input != 0 || count != 0)
println(count)
}
You can use maxOf() for example:
fun readLn() = readLine()!!
fun readIntOrStopLoopWithZero() = readLn().toIntOrNull() ?: 0
fun readMaxInteger() {
var maxInput: Int = 0
//getting first user input
var userInput = readIntOrStopLoopWithZero()
while (userInput!= 0) {
//calc max
maxInput = maxOf(userInput, maxInput)
//getting user input again.
userInput= readIntOrStopLoopWithZero()
}
if (maxInput == 0)
//In case we get 0 as first input.
print("max value not found")
else
print("Max input is : $maxInput")
}
readMaxInteger()
Input:
1
2
0
output:
Max input is 2
Input:
1
0
output:
Max input is 1
Input:
0
output:
max value not found
Input:
1
2
3
Word
output:
Max input is : 3
Problems:
You've over-complicated it by creating a nested loop.
You're checking input != 0 to exit either of the loops, so it will exit immediately on the first element.
So fixing these problems, you have:
fun main(args: Array<String>) {
val scanner = Scanner(System.`in`)
var count = 0 // start with 0 for simplicity
do {
val input = scanner.nextInt()
if (count < input) {
count = input
}
} while (input != 0)
println(count)
}
And here's an alternate solution. Make a Sequence so it becomes an Iterable and you can use maxOrNull() on it:
fun main(args: Array<String>) {
val sequence = sequence {
val scanner = Scanner(System.`in`)
do {
val input = scanner.nextInt()
yield(input)
} while (input != 0)
}
println(sequence.maxOrNull() ?: 0)
}
I wanted to finish within a single function, so I chose the following.
fun main(args: Array<String>) {
val scanner = Scanner(System.`in`)
var count = 0 // start with 0 for simplicity
do {
val input = scanner.nextInt()
if (count < input) {
count = input
}
} while (input != 0)
println(count)
}
Thanks to everyone who responded!
I am new in learning Kotlin. I try to using tail recursion for calculating factorial in my code, but when I enter 99999, the output is 0. Here is my code:
fun main(){
println(factTailRec(99999))
}
tailrec fun factTailRec(number: Int, result: Int = 1): Long{
return if(number.toInt() == 1){
result.toLong()
} else {
factTailRec(number-1, result*number)
}
}
Your result of type Int is not sufficient to hold the actual resulting value. You need something like BigInteger:
import java.math.BigInteger
import java.math.BigInteger.ONE
inline operator fun BigInteger.times(other: Int): BigInteger =
this.multiply(BigInteger.valueOf(other.toLong()))
tailrec fun factTailRec(number: Int, result: BigInteger = ONE): BigInteger {
return if (number == 1) {
result
} else {
factTailRec(number - 1, result * number)
}
}
fun main() {
println(factTailRec(99999)) // 28242294079...
}
I've been wondering if there is any chance of implementing sequence which executes some operation n times using map operator, but can terminate that execution during processing on the 'go'? Here is an imperative code which I'm trying to implement using Kotlin sequences:
val offers = mutableListOf<String>()
for (pageNumber in FIRST_PAGE_NUMBER until numberOfPages) {
val offersInPage = findByPage(query, pageSize, pageNumber)
offers.addAll(offersInPage)
if(offersInPage.size == 5)
break
}
The main thing is that I would like to hang up processing any further requests to external service when response from the previous one meets some conditions.
When trying to implement it in more declarative way I ended up with something like this:
IntArray(numberOfPages)
.asSequence()
.map { findByPage(query, pageSize, it) }
.takeWhile { it.size == 5 }
.flatten()
.toList()
But the findByQuery method is invoked n times and then the result is filtered. Is there any operator which help me implement something like terminating that lazy operation once given condition is met?
The sequence works exactly as you expected. It performs on map checks the takeWhile condition and does only continue if it is true.
One problem could be IntArray(numberOfPages). This creates an array of 0 of the size numberOfPages. So you iterate over a sequence of 0 and not the page numbers. You can simply change this to the for loop condition.
(FIRST_PAGE_NUMBER until numberOfPages)
.asSequence()
Another problem could be the takeWhile. In the for loop you stop after the first element with a size == 5. But in the sequence you stop before the first element with a size != 5. The simplest solution for this problem is to find another condition that would break the loop before offers.addAll(offersInPage) will be executed. If this is not possible you can use something like this:
fun <T> Sequence<T>.takeWhileEndInclusive(predicate: (T) -> Boolean) = object : Sequence<T> {
val sequence = this#takeWhileEndInclusive
override fun iterator() = object : Iterator<T> {
val iterator = sequence.iterator()
var nextState: Int = -1
var nextItem: T? = null
var found = false
private fun calcNext() {
if (!found && iterator.hasNext()) {
val item = iterator.next()
if (!predicate(item)) {
found = true
}
nextState = 1
nextItem = item
return
}
nextState = 0
}
override fun next(): T {
if (nextState == -1)
calcNext()
if (nextState == 0)
throw NoSuchElementException()
#Suppress("UNCHECKED_CAST")
val result = nextItem as T
nextItem = null
nextState = -1
return result
}
override fun hasNext(): Boolean {
if (nextState == -1)
calcNext()
return nextState == 1
}
}
}
This is a slightly adjusted version of the default takeWhile implementation.
Usage:
(FIRST_PAGE_NUMBER until numberOfPages)
.asSequence()
.map { findByPage(query, pageSize, it) }
.takeWhileEndInclusive { it.size != 5 }
.flatten()
.toList()
Is it possible to get the object's memory consumption, like in c++ with sizeOf()?
for object is Parcelable, seems this one could get the raw bytes, not sure if it is accurate for the size of that parcelable objec:
fun marshall(parceable: Parcelable): ByteArray {
val parcel = Parcel.obtain()
parceable.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.recycle()
return bytes
}
Any suggestion?
Update:
Thanks #Gustavo Passini pints to a link with a lot approaches. Someone needs java.lang.instrument package and not able to use on Android, and found one but needs api level 26 above, converted into koltin and copied it here as ref (thanks Agnius Vasiliauskas ), is it accurate?:
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.io.Serializable
#TargetApi (26)
object ObjectSizeCalculator {
private fun getFirstObjectReference(o: Any): Any? {
val objectType = o.javaClass.typeName
if (objectType.substring(objectType.length - 2) == "[]") {
try {
return if (objectType == "java.lang.Object[]")
(o as Array<Any>)[0]
else if (objectType == "int[]")
(o as IntArray)[0]
else
throw RuntimeException("Not Implemented !")
} catch (e: IndexOutOfBoundsException) {
return null
}
}
return o
}
fun getObjectSizeInBytes(o: Any?): Int {
val STRING_JAVA_TYPE_NAME = "java.lang.String"
if (o == null)
return 0
val objectType = o.javaClass.typeName
val isArray = objectType.substring(objectType.length - 2) == "[]"
val objRef = getFirstObjectReference(o)
if (objRef != null && objRef !is Serializable)
throw RuntimeException("Object must be serializable for measuring it's memory footprint using this method !")
try {
val baos = ByteArrayOutputStream()
val oos = ObjectOutputStream(baos)
oos.writeObject(o)
oos.close()
val bytes = baos.toByteArray()
var i = bytes.size - 1
var j = 0
while (i != 0) {
if (objectType !== STRING_JAVA_TYPE_NAME) {
if (bytes[i].toInt() == 112)
return if (isArray)
j - 4
else
j
} else {
if (bytes[i].toInt() == 0)
return j - 1
}
i--
j++
}
} catch (e: Exception) {
return -1
}
return -1
}
}