Kotlin - How to split list of strings based on the total length of characters - kotlin

I am writing a program in Kotlin that is posting messages to a rest endpoint.
val messages : List<String> = getMessageToSend();
webClient
.post()
.uri { builder -> builder.path("/message").build() }
.bodyValue(PostMessageRequest(
messages.joinToString("\n")
))
.exchange()
.block()
However, the rest endpoint has a limit on the maximum size of messages sent. I'm fairly new to Kotlin, but I was looking for a functional way to achieve this, and i'm struggling. I know how I would write this in Java, but i'm keen to do it right. I want to split the messages list into a list of lists, with each list limited to the maximum size allowed and only whole strings added, and then post them individually. I've had a look at methods like chunked, but that doesn't seem flexible enough to achieve what i'm trying to do.
For example, if my message was [this, is, an, example] and the limit was 10, i'd expect my list of lists to be [[this, is an], [example]]
Any suggestions would be massively appreciated.

This looks rather like a situation I've hit before.  To solve it, I wrote the following general-purpose extension function:
/**
* Splits a collection into sublists not exceeding the given size. This is a
* generalisation of [List.chunked]; but where that limits the _number_ of items in
* each sublist, this limits their total size, according to a given sizing function.
*
* #param maxSize None of the returned lists will have a total size greater than this
* (unless a single item does).
* #param size Function giving the size of an item.
*/
inline fun <T> Iterable<T>.chunkedBy(maxSize: Int, size: T.() -> Int): List<List<T>> {
val result = mutableListOf<List<T>>()
var sublist = mutableListOf<T>()
var sublistSize = 0L
for (item in this) {
val itemSize = item.size()
if (sublistSize + itemSize > maxSize && sublist.isNotEmpty()) {
result += sublist
sublist = mutableListOf()
sublistSize = 0
}
sublist.add(item)
sublistSize += itemSize
}
if (sublist.isNotEmpty())
result += sublist
return result
}
The implementation's a bit hairy, but it's pretty straightforward to use.  In your case, I expect you'd do something like:
messages.chunkedBy(1024){ length + 1 }
.map{ it.joinToString("\n") }
to give a list of strings, each no more than 1024 chars*. (The + 1 is of course to allow for the newline characters.)
I'm surprised something like this isn't in the stdlib, to be honest.
(* Unless any of the initial strings is longer.)

You can split a List into chunks of a given length by using chunked like this
fun main() {
val messages = listOf(1, 2, 3, 4, 5, 6)
val chunks = messages.chunked(3)
println("$messages ==> $chunks")
}
This prints
[1, 2, 3, 4, 5, 6] ==> [[1, 2, 3], [4, 5, 6]]

Related

Is there an easy way to multiply each element with each other in array / list - Kotlin?

I have got {1,2,3} / or it might be a list<Int> is there an easy way to multiply each element with each other like 1*2 , 1*3, 2*3 ?
This should work, given that you probably don't want to include the duplicates like items[i] * items[j] and items[j] * items[i]
val items = listOf(1, 2, 3, 4)
val result = items.flatMapIndexed { index, a ->
items.subList(index + 1, items.size).map { b -> a * b }
}
println(result) // [2, 3, 4, 6, 8, 12]
flatMapIndexed builds a list for each of the items elements by evaluating the lambda on the index and the item, and then concatenates the lists.
subList is an efficient way to take the items in the specific range: starting at the next index, and until the end of the list.
You can try the old-fashioned way: nested loops
fun main(args: Array<String>) {
val list = listOf( 1, 2, 3 )
for (i in list.indices) {
for (j in (i + 1) until list.size) {
println("${list[i]} * ${list[j]} = ${list[i] * list[j]}")
}
}
}
Output of this code:
1 * 2 = 2
1 * 3 = 3
2 * 3 = 6
If you did want all the permutations (every possible ordering), you could do something like this:
val result = with(items) {
flatMapIndexed { i, first ->
slice(indices - i).map { second -> // combine first and second here }
}
}
slice lets you provide a list of indices for the elements you want to pull, so you can easily exclude the current index and get all the other elements to combo it with. Takes an IntRange too, so you can do slice(i+1 until size) to get the combination (every pairing) functionality too.
Not as efficient as hotkey's subList version (since that doesn't make a copy) but you can get two behaviours this way so I thought I'd mention it! But if I were making a reusable function rather than a quick one-liner, I'd probably go with deHaar's approach with the nested for loops, it's efficient and easy enough to tweak for either behaviour

What is the function of the * operation in Kotlin?

The Code A is from the offical sample project.
I don't understand what val tasks = remember { mutableStateListOf(*allTasks) } mean, could you tell me ?
BTW, Android Studio give me some information, you can see Image A
Code A
#Composable
fun Home() {
// String resources.
val allTasks = stringArrayResource(R.array.tasks)
val allTopics = stringArrayResource(R.array.topics).toList()
// The currently selected tab.
var tabPage by remember { mutableStateOf(TabPage.Home) }
// True if the whether data is currently loading.
var weatherLoading by remember { mutableStateOf(false) }
// Holds all the tasks currently shown on the task list.
val tasks = remember { mutableStateListOf(*allTasks) }
...
}
Image A
From the documentation of varargs:
When you call a vararg -function, you can pass arguments individually, for example asList(1, 2, 3). If you already have an array and want to pass its contents to the function, use the spread operator (prefix the array with *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
As you see, it expands an array to multiple values for use in a vararg. If you havd an array containing the elements 1, 2, 3, you can pass *yourArray to a method that is equivalent to yourMethod(1,2,3).
In Kotlin * is the Spread Operator.
From docs :
When you call a vararg -function, you can pass arguments individually, for example asList(1, 2, 3). If you already have an array and want to pass its contents to the function, use the spread operator (prefix the array with *):
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
In this case tasks will contain the list of strings from R.array.tasks

In Kotlin, can I have two random values with the second one omitting the first random number?

Here is what I am trying to say:
val firstNumbers = (1..69).random()
val secondNumbers = (1..69).random()
I would like the secondNumbers to omit the random number picked in firstNumbers
If you're just generating two numbers, what you could do is lower the upper bound for secondNumbers down to 68, then add 1 if it's greater than or equal to the first number. This will ensure an even distribution:
val firstNumber = (1..69).random()
var secondNumber = (1..68).random()
if (secondNumber >= firstNumber) {
secondNumber += 1
}
For generating more than 2 numbers, the following code should work:
fun randoms(bound: Int, n: Int): List<Int> {
val mappings = mutableMapOf<Int, Int>()
val ret = mutableListOf<Int>()
for (i in 0 until n) {
val num = (1..(bound - i)).random()
ret.add(mappings.getOrDefault(num, num))
mappings.put(num, mappings.getOrDefault(bound - i, bound - i))
}
return ret
}
It tries to emulate Fisher-Yates shuffling while only keeping track of swaps that happened, thus greatly reducing memory usage when n is much less than bound. If n is very close to bound, then the answer by #lukas.j is much cleaner to use and probably also faster.
It can be used like so:
randoms(69, 6) // might return [17, 36, 60, 48, 69, 21]
(I'd encourage people to double-check the uniformity and correctness of the algorithm, but it seems good to me)
random() is the wrong approach, rather use shuffled() and then take the first two elements from the list with take(). And it is a oneliner:
val (firstNumber, secondNumber) = (1..69).shuffled().take(2)
println(firstNumber)
println(secondNumber)
Another approach could be to find one number in range 1..69, remove that number from the range and find the second one.
val first = (1..69).random()
val second = ((1..69) - first).random()
Edit: As per your comment, you want 6 different numbers within this range. You can do that like this.
val values = (1..69).toMutableList()
val newList = List(6) {
values.random().also { values.remove(it) }
}

How I can return IntArray with reduce or fold?

I have the following data for my task:
Input: nums = [1,2,3,4]
Output: [1,3,6,10]
Explanation: Running sum is obtained as follows: [1, 1+2, 1+2+3, 1+2+3+4].
As u see, I need to return IntArray, the first thing I used was runningReduce() , but this function is used in the version of Kotlin 1.4.30.
fun runningSum(nums: IntArray): IntArray {
return nums.runningReduce { sum, element -> sum + element }.toIntArray()
}
Yes, this solution works, but how can I solve the same problem using reduce() or fold()?
Try the following:
nums.fold(listOf(0)) { acc, i -> acc + (acc.last() + i) }.drop(1).toIntArray()
The solution is sub-optimal though: it copies the list in each iteration. But looks fancy.
To avoid copying, you could write it as follows:
nums.fold(mutableListOf(0)) { acc, i -> acc += (acc.last() + i); acc }.drop(1).toIntArray()
I think I prefer the first version, the second one is not pure from functional perspective.
Mafor is right, fold() is not a good choice when producing a collection because it copies the collection every time so you need to workaround with mutable collections, which defeats the point of the functional style.
If you really want to work with arrays, which are mutable, doing it the old fashioned procedural way may be best:
val array = intArrayOf(1, 2, 3, 4)
for (i in array.indices.drop(1)) {
array[i] += array[i - 1]
}
println(array.joinToString(", "))
Here's a slightly modified version of Mafor's answer that gives you an IntArray and avoids the use of multiple statements - it still uses the mutable list though:
val input = intArrayOf(1, 2, 3, 4)
val output = input.fold(mutableListOf(0)) { acc, cur ->
acc.apply { add(last() + cur) }
}.drop(1).toIntArray()
println(output.joinToString(", "))
Both of these print 1, 3, 6, 10.

Difference between fold and reduce in Kotlin, When to use which?

I am pretty confused with both functions fold() and reduce() in Kotlin, can anyone give me a concrete example that distinguishes both of them?
fold takes an initial value, and the first invocation of the lambda you pass to it will receive that initial value and the first element of the collection as parameters.
For example, take the following code that calculates the sum of a list of integers:
listOf(1, 2, 3).fold(0) { sum, element -> sum + element }
The first call to the lambda will be with parameters 0 and 1.
Having the ability to pass in an initial value is useful if you have to provide some sort of default value or parameter for your operation. For example, if you were looking for the maximum value inside a list, but for some reason want to return at least 10, you could do the following:
listOf(1, 6, 4).fold(10) { max, element ->
if (element > max) element else max
}
reduce doesn't take an initial value, but instead starts with the first element of the collection as the accumulator (called sum in the following example).
For example, let's do a sum of integers again:
listOf(1, 2, 3).reduce { sum, element -> sum + element }
The first call to the lambda here will be with parameters 1 and 2.
You can use reduce when your operation does not depend on any values other than those in the collection you're applying it to.
The major functional difference I would call out (which is mentioned in the comments on the other answer, but may be hard to understand) is that reduce will throw an exception if performed on an empty collection.
listOf<Int>().reduce { x, y -> x + y }
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.
This is because .reduce doesn't know what value to return in the event of "no data".
Contrast this with .fold, which requires you to provide a "starting value", which will be the default value in the event of an empty collection:
val result = listOf<Int>().fold(0) { x, y -> x + y }
assertEquals(0, result)
So, even if you don't want to aggregate your collection down to a single element of a different (non-related) type (which only .fold will let you do), if your starting collection may be empty then you must either check your collection size first and then .reduce, or just use .fold
val collection: List<Int> = // collection of unknown size
val result1 = if (collection.isEmpty()) 0
else collection.reduce { x, y -> x + y }
val result2 = collection.fold(0) { x, y -> x + y }
assertEquals(result1, result2)
Another difference that none of the other answers mentioned is the following:
The result of a reduce operation will always be of the same type (or a super type) as the data that is being reduced.
We can see that from the definition of the reduce method:
public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S {
val iterator = this.iterator()
if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
var accumulator: S = iterator.next()
while (iterator.hasNext()) {
accumulator = operation(accumulator, iterator.next())
}
return accumulator
}
On the other hand, the result of a fold operation can be anything, because there are no restrictions when it comes to setting up the initial value.
So, for example, let us say that we have a string that contains letters and digits. We want to calculate the sum of all the digits.
We can easily do that with fold:
val string = "1a2b3"
val result: Int = string.fold(0, { currentSum: Int, char: Char ->
if (char.isDigit())
currentSum + Character.getNumericValue(char)
else currentSum
})
//result is equal to 6
reduce - The reduce() method transforms a given collection into a single result.
val numbers: List<Int> = listOf(1, 2, 3)
val sum: Int = numbers.reduce { acc, next -> acc + next }
//sum is 6 now.
fold - What would happen in the previous case of an empty list? Actually, there’s no right value to return, so reduce() throws a RuntimeException
In this case, fold is a handy tool. You can put an initial value by it -
val sum: Int = numbers.fold(0, { acc, next -> acc + next })
Here, we’ve provided initial value. In contrast, to reduce(), if the collection is empty, the initial value will be returned which will prevent you from the RuntimeException.
Simple Answer
Result of both reduce and fold is "a list of items will be transformed into a single item".
In case of fold,we provide 1 extra parameter apart from list but in case of reduce,only items in list will be considered.
Fold
listOf("AC","Fridge").fold("stabilizer") { freeGift, itemBought -> freeGift + itemBought }
//output: stabilizerACFridge
In above case,think as AC,fridge bought from store & they give stabilizer as gift(this will be the parameter passed in the fold).so,you get all 3 items together.Please note that freeGift will be available only once i.e for the first iteration.
Reduce
In case of reduce,we get items in list as parameters and can perform required transformations on it.
listOf("AC","Fridge").reduce { itemBought1, itemBought2 -> itemBought1 + itemBought2 }
//output: ACFridge
The difference between the two functions is that fold() takes an initial value and uses it as the accumulated value on the first step, whereas the first step of reduce() uses the first and the second elements as operation arguments on the first step.