15
votes

I'm new to Kotlin (I have a Java background) and I can't seem to figure out how to check whether a string contains a match from a list of keywords.

What I want to do is check if a string contains a match from an array of keywords (case-insensitive please). If so, print out the keyword(s) that was matched and the string that contained the keyword. (I will be looping over a bunch of strings in a file).

Here's an MVE for starters:

val keywords = arrayOf("foo", "bar", "spam")

fun search(content: String) {
    var match = <return an array of the keywords that content contained>
    if(match.size > 0) {
          println("Found match(es): " + match + "\n" + content)
    }
}   

fun main(args: Array<String>) {
    var str = "I found food in the barn"
    search(str) //should print out that foo and bar were a match
}

As a start (this ignores the 'match' variable and getting-a-list-of-keywords-matched), I tried using the following if statement according with what I found at this question,

if(Arrays.stream(keywords).parallel().anyMatch(content::contains))

but it put a squiggly line under "content" and gave me this error

None of the following functions can be called with the arguments supplied: public operator fun CharSequence.contains(char: Char, ignoreCase: Boolean = ...): Boolean defined in kotlin.text public operator fun CharSequence.contains(other: CharSequence, ignoreCase: Boolean = ...): Boolean defined in kotlin.text @InlineOnly public inline operator fun CharSequence.contains(regex: Regex): Boolean defined in kotlin.text

3

3 Answers

24
votes

You can use the filter function to leave only those keywords contained in content:

val match = keywords.filter { it in content }

Here match is a List<String>. If you want to get an array in the result, you can add .toTypedArray() call.

in operator in the expression it in content is the same as content.contains(it).

If you want to have case insensitive match, you need to specify ignoreCase parameter when calling contains:

val match = keywords.filter { content.contains(it, ignoreCase = true) }
3
votes

Another obvious choice is using a regex doing case-insensitive matching:

arrayOf("foo", "bar", "spam").joinToString(prefix = "(?i)", separator = "|").toRegex())

Glues together a pattern with a prefixed inline (?i) incase-sensitive modifier, and alternations between the keywords: (?i)foo|bar|spam

Sample Code:

private val keywords = arrayOf("foo", "bar", "spam")
private val pattern = keywords.joinToString(prefix = "(?i)", separator = "|")
private val rx = pattern.toRegex()

fun findKeyword(content: String): ArrayList<String> { 
    var result = ArrayList<String>()
    rx.findAll(content).forEach { result.add(it.value) }
    return result
}

fun main(args: Array<String>) { 
    println(findKeyword("Some spam and a lot of bar"));
}

The regex approach could be handy if you are after some more complex matching, e.g. non-/overlapping matches adding word boundaries \b, etc.

0
votes

Here is my approach without Streams:

fun String.containsAnyOfIgnoreCase(keywords: List<String>): Boolean {
    for (keyword in keywords) {
        if (this.contains(keyword, true)) return true
    }
    return false
}

Usage:

"test string".containsAnyOfIgnoreCase(listOf("abc","test"))