I have a mutable list of strings and I'm trying to remove a word from each of them. The problem is that I don't believe the word is being removed from each line.
for (item in stringLines) {
when {
item.contains("SUMMARY") -> eventLines.add(item)
item.contains("DTSTART") -> startDateLines.add(item)
item.contains("DTEND") -> endDateLines.add(item)
//item.contains("URL:") -> locationLines.add(item)
//item.contains("CATEGORIES") -> categoriesLines.add(item)
}
}
for (item in eventLines) {
when{
item.contains("SUMMARY") -> item.removePrefix("SUMMARY")
}
}
The string is
SUMMARY WINTER FORMAL
and I need
WINTER FORMAL
but whatever reason it just doesn't appear. I know how to do this in java but I have no idea how to do this in Kotlin and I could not find any function that was able to accomplish this. What am I missing?
removePrefix removes the specific part of the string only if that is the prefix. Obviously, this is not your case. You could do a split, filter, join sequence to yield your expected result.
println("SUMMARY WINTER FORMAL".split(" ").filter { it != "SUMMARY" }.joinToString(" "))
//output : "WINTER FORMAL"
But if you have multiple contiguous spaces, they will become one space in the output. Also this method does not work if "SUMMARY" is not surrounded by spaces, e.g. "SUMMARYWINTERFORMAL" won't be changed.
You can remove part of the string by replacing it with empty string, but note that this will keep any surrounding characters including the surrounding spaces.
println("SUMMARY WINTER FORMAL".replace("SUMMARY",""))
//output : " WINTER FORMAL"
Note that there is a space in front of "WINTER"
If you wan't to include the spaces if any, using Regular Expression would do:
println("SUMMARY WINTER FORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTER FORMAL"
println("SUMMARYWINTERFORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTERFORMAL"
println(" SUMMARY WINTER FORMAL".replace("\\s*SUMMARY\\s*".toRegex(),""))
//output : "WINTER FORMAL"
You can modify the regular expression even more to better suit your use case.
Strings in Kotlin (and Java) are immutable. So item.removePrefix returns a new String and you have to update the list or create a new list like this:
for (item in eventLines) {
when {
item.contains("SUMMARY") ->
eventLinesWithPrefixRemoved.add(item.removePrefix("SUMMARY"))
else -> eventLinesWithPrefixRemoved.add(item)
}
}
If you want only to remove 'SUMMARY' from the string, you can use this case
val l = listOf("SUMMARY WINTER FORMAL", "SUMMARY FORMAL", "WINTER FORMAL", "SUMMARY WINTER")
println(
l.map {
it.split(" ")
.filter { s -> s != "SUMMARY" }
.joinToString(" ")
}
) // puts [WINTER FORMAL, FORMAL, WINTER FORMAL, WINTER]
Related
I have random pojo values, and I want to sort by one of the POJO values, the structure of the values I want to sort is as follows: A-1, A-2, A-3, A-4, A-5, .. .
I've tried to sort it but the value is really in order only between A-1 to A-9, while the value for A-10 is always under A-1.
here's the code I use:
temp.sortWith { lhs, rhs ->
rhs?.blok_kamar?.let {
lhs?.blok_kamar?.compareTo(
it, false
)}!!
}
Please help.
This is how lexicographical ordering works. "1000" is less than "2", despite 1000 being greater than 2. Look at the quotes. It seems like your A-1, A-2, ..., A-10 are Strings. You need to extract the numbers from those identifiers and compare them as Ints:
data class Data(val id: String)
fun main() {
val list = mutableListOf(
Data("A-1"),
Data("A-10"),
Data("A-2"),
Data("A-30"),
Data("A-3")
)
list.sortBy { it.id.substring(it.id.indexOf("-") + 1).toInt() }
println(list)
}
This prints:
[Data(id=A-1), Data(id=A-2), Data(id=A-3), Data(id=A-10), Data(id=A-30)]
Base on your code I do not know what temp actually is, so here is an idea to give you a start, based on a list:
val result = list
.sortedBy {
it.id.substringBefore("-") + "-" + it.id.substringAfter("-").padStart(3, '0')
}
Hey i have nested list and i wanted find first occurrence index value.
data class ABC(
val key: Int,
val value: MutableList<XYZ?>
)
data class XYZ)
val isRead: Boolean? = null,
val id: String? = null
)
I added code which find XYZ object, but i need to find index. So how can i achieved in efficient way. How can i improve my code?
list?.flatMap { list ->
list.value
}?.firstOrNull { it?.isRead == false }
If you would like to stick to functional style then you can do it like this:
val result = list.asSequence()
.flatMapIndexed { outer, abc ->
abc.value.asSequence()
.mapIndexed { inner, xyz -> Triple(outer, inner, xyz) }
}
.find { it.third?.isRead == false }
if (result != null) {
val (outer, inner) = result
println("Outer: $outer, inner: $inner")
}
For each ABC item we remember its index as outer and we map/transform a list of its XYZ items into a list of tuples: (outer, inner, xyz). Then flatMap merges all such lists (we have one list per ABC item) into a single, flat list of (outer, inner, xyz).
In other words, the whole flatMapIndexed() block changes this (pseudo-code):
[ABC([xyz1, xyz2]), ABC([xyz3, xyz4, xyz5])]
Into this:
[
(0, 0, xyz1),
(0, 1, xyz2),
(1, 0, xyz3),
(1, 1, xyz4),
(1, 2, xyz5),
]
Then we use find() to search for a specific xyz item and we acquire outer and inner attached to it.
asSequence() in both places changes the way how it works internally. Sequences are lazy, meaning that they perform calculations only on demand and they try to work on a single item before going to another one. Without asSequence() we would first create a full list of all xyz items as in the example above. Then, if xyz2 would be the one we searched, that would mean we wasted time on processing xyz3, xyz4 and xyz5, because we are not interested in them.
With asSequence() we never really create this flat list, but rather perform all operations per-item. find() asks for next item to check, mapIndexed maps only a single item, flatMapIndexed also maps only this single item and if find() succeed, the rest of items are not processed.
In most cases using sequences here could greatly improve the performance. In some cases, like for example when lists are small, sequences may degrade the performance by adding an overhead. However, the difference is very small, so it is better to leave it as it is.
As we can see, functional style may be pretty complicated in cases like this. It may be a better idea to use imperative style and good old loops:
list.indicesOfFirstXyzOrNull { it?.isRead == false }
inline fun Iterable<ABC>.indicesOfFirstXyzOrNull(predicate: (XYZ?) -> Boolean): Pair<Int, Int>? {
forEachIndexed { outer, abc ->
abc.value.forEachIndexed { inner, xyz ->
if (predicate(xyz)) {
return outer to inner
}
}
}
return null
}
In Kotlin, you can use the indexOf() function that returns the index of the first occurrence of the given element, or -1 if the array does not contain the element.
Example:
fun findIndex(arr: Array<Int>, item: Int): Int {
return arr.indexOf(item)
}
I've used the sortedWith and compareBy methods before and it is capable of sorting using multiple parameters.
I've also used nullsLast method before and it is quite convenient just like the other two.
With these methods, however, I can't figure out a way to sort a collection with the following sorting rules:
Given the Class:
data class MyClass(
val varA: String?,
val varB: String
)
sort according to varA alphabetically and nulls/blank to be last; then
sort according to varB alphabetically
So let's say I have the following collection:
val collection = listOf(
MyClass(null, "A"),
MyClass("a", "B"),
MyClass("c", "C"),
MyClass("b", "D"),
MyClass("", "E"),
MyClass("a", "F"),
MyClass("b", "G"),
MyClass(null, "H"),
MyClass("", "I")
)
it should then be sorted to:
MyClass("a", "B")
MyClass("a", "F")
MyClass("b", "D")
MyClass("b", "G")
MyClass("c", "C")
MyClass(null, "A")
MyClass("", "E")
MyClass(null, "H")
MyClass("", "I")
is there a one-liner code I can use just like the compareBy that uses vararg parameter
I would do it like this (see kotlin.comparisons documentation):
val comparator = compareBy<String, MyClass>(nullsLast(), { it.varA.let { if (it == "") null else it } }).thenBy { it.varB }
collection.sortedWith(comparator)
Of course there is a one-liner for this:
collection.sortedBy {if(it.varA != null && it.varA.isNotEmpty()) it.varA + it.varB else "z" + it.varB}
Then print it:
output.forEach { println("Myclass(${it.varA}, ${it.varB})") }
Output:
Myclass(a, B)
Myclass(a, F)
Myclass(b, D)
Myclass(b, G)
Myclass(c, C)
Myclass(null, A)
Myclass(, E)
Myclass(null, H)
Myclass(, I)
Explanation:
We need to split the cases when we sort by varA and when by varB here. That is why there is a condition if(it.varA != null && it.varA.isNotEmpty()), which says that we will sort by varA only if its value isn't null or "".
Otherwise, we sort by string "z" + it.varB, which means that we sort by varB, but with z prefix, which will ensure that these items will be at the end of sorted collection.
To understand more easily how it works, you can try following code:
val output = collection.map { if(it.varA != null && it.varA.isNotEmpty()) it.varA + it.varB else "z" + it.varB }
output.sortedBy { it }.forEach { println(it) }
This one will output these strings:
aB
aF
bD
bG
cC
zA
zE
zH
zI
Now it should be more obvious. :-)
I have a problem that I really cannot solve.. Maybe You may help me. I need to sort an object list from API return that contains filters. The problem is those filters are dynamic, The object Order (the problematic filter) :
class Order(val field : String, val direction: String)
The field is an object attribute (column), the direction can be ASC or DESC.
The JSON can return more than one filter, so this can be :
order : {
field : "id",
direction : "ASC"
},
{
field : "creationDate"
direction : "DESC"
}
The problem is, I don't know how to create a dynamic function that can create a perfect sort in my list. I know I've got to do this :
return list.sortedWith(compareBy(List::id).thenByDescending(List::creationDate))
But Dynamically.. wow
KT
You can create a map from a property name to the comparator that compares orders by that property:
val comparators = mapOf<String, Comparator<Order>>(
"field" to compareBy { it.field },
"direction" to compareBy { it.direction }
)
Then you can pick comparators from that map by the given property names, change their sorting order with Comparator.reversed() extension function, and finally combine all these comparators into the single resulting comparator with Comparator.then(Comparator) function:
val givenOrder = listOf("field" to "ASC", "direction" to "DESC")
val resultingOrder = givenOrder
.map { (fieldName, direction) ->
comparators[fieldName]!!.let { if (direction == "DESC") it.reversed() else it }
}
.reduce { order, nextComparator -> order.then(nextComparator) }
val sortedList = list.sortedWith(resultingOrder)
I am guessing that the second ordering oly applies to those where the first is the same value
sortedWith + compareBy
compareBy takes a vararg of selectors which is just a array, so it can be constructed
val selectors: Array<(T) -> Comparable<*>?> = orders.map { TODO() }.toArray()
list.sortedWith(compareBy(*selectors))
i am thinking some extra function go go though all possible fields you could sort and uses either it.field or -(it.field) to create the selectors
also see this answer: Sort collection by multiple fields in Kotlin
I'm trying to store certain lines from an .ics file to separate strings depending on their contents. I've been able to convert an .ics file to a string, but I am having difficulty searching it line by line to find certain keywords.
The string (and file) contains:
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |
I've been able to display the text in the logcat, but I have not been able to save the lines as separate strings.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var textView = findViewById<TextView>(R.id.textView)
val file_name = "education-1e1a4bdab8e.ics"
val ics_string = application.assets.open(file_name).bufferedReader().use {
it.readText()
}
Log.i("TAG", ics_string)
textView.text = ics_string
if (ics_string.contains("BEGIN:VCALENDAR", ignoreCase = true))
{
Log.i("TAG", "contains event")
}
}
The logcat confirms that the text is in the document, but not which line.
Is there any way to add lines of a text as separate strings?
Just looking at the BufferedReader you have already 4 functions that all give you the lines.
readLines gives you a List<String> containing all the lines
useLines lets you use a sequence of lines which you can then transform and assures that after calling it, the reader is closed
lineSequence() returns a sequence of the lines, but does not close the reader after calling it
lines() returns a Stream<String> containing the lines and basically comes from the BufferedReader itself. As you are using Kotlin you probably do not want to use this method.
useLines and readLines are also available on File itself
As I am not sure what you really want to accomplish I recommend you start with readLines directly. The ics-file is usually rather small and with the lines you can still filter/map whatever you want. The next best candidate then is probably either useLines or lineSequence. It really depends on what you do next.
You can use the extension function on String, lines(), something like this:
fun lines() {
val text = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//School of Rochester NY |-ECPv4.8.1//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:School of Rochester NY |"""
val foundLines = text.lines().map {
it.contains("BEGIN:VCALENDAR") to it
}.filter { (contains, _) -> contains }
.map { it.second }
println(foundLines)
}
fun main() {
lines()
}