Reputation: 1664
I'm looking for an most efficient way in Kotlin/Java to filter a List
down by a certain count and with the removal of filtered elements being applied across the collection in a uniformed fashion (i.e. - the elements to be removed span across the entire collection evenly);
For Example
filter the following down to 5 [0,1,2,3,4,5,6,7,8,9] = [0,2,4,6,8]
filter the following down to 2 [1,100,1000,10000] = [1,1000]
I came up with the following Kotlin extension function but it isn't working as expected (its return more than the requested count - e.g. initial size = 2593, newcount=125 - list=130)
fun <T> List<T>.filterDownByToCount(newCount: Int): List<T> {
if (newCount < 0 || newCount >= this.size)
throw IllegalArgumentException("prop ($newCount) must be between 1 and $newCount")
val ratio = size / newCount
val list = this.filterIndexed { index, _ -> index % ratio == 0 }
return list
}
ideally this function can handle anything from 0
up to list.size-1
& I would like to use a library to do this (if possible) but can't seem to find any that fits my use case.
Upvotes: 0
Views: 1452
Reputation: 31269
The problem is that the ratio is actually a fractional number, but you're doing integer arithmetic.
fun <T> List<T>.filterDownByToCount(newCount: Int): List<T> {
if (newCount < 0 || newCount >= this.size)
throw IllegalArgumentException("prop ($newCount) must be between 1 and $newCount")
val ratio = size.toDouble() / newCount
var accu = 0.0
val list = this.filter { _ ->
(accu <= 0).also { if (accu <= 0) accu += ratio; accu-- }
}
return list
}
You can change the ratio to be a Double
- for your example, this returns a 125 element result.
Upvotes: 1
Reputation: 140319
The problem is that size / newCount
is the floor of the ratio, so the ratio is too small, and hence you get more elements than you expect.
Use the ceiling instead:
val ratio = (size + newCount - 1) / newCount
For your example, this results in a list of size 124.
Upvotes: 1