Test if string contains anything from an array of strings (kotlin) - arrays

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

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) }

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.

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"))

I think Any is the efficient way.
fun findMatch(s: String, strings: List<String>): Boolean {
return strings.any { s.contains(it) }
}
fun main() {
val today = "Wednesday"
val weekend = listOf("Sat", "Sun")
println(if (findMatch(today, weekend)) "Yes" else "No") // No
}
reference: click here

Related

How to alter part of an array element in Swift?

Say I have the following array:
var questions = ["Is he great?", "Is he nice?", "Is he wonderful?"]
How can I alter it so that if I call any element from this array, the 'he' is substituted for 'she'? I've tried map, but this only seems to work for individual elements, and not parts of an element. Slicing is also only inter-element, and replaceOccurrence creates a new string, but doesn't alter the original. Ideally I'd like to write:
print(questions[0])
and get: "Is she great?" for any element in the array containing 'he' without having to create a whole new array. TIA.
I am not sure why you want to avoid creating a new array, but if you're determined to avoid making new arrays, consider an array of functions that return the thing you want, like:
var sheOrHe = "he"
func f(_ s: String) -> () -> String {
return { "Is \(sheOrHe) \(s)?" }
}
let qFuncs = [f("great"), f("nice"), f("wonderful")]
qFuncs[0]() // "Is he great?"
sheOrHe = "she"
qFuncs[0]() // "Is she great?"
Or without closing over the sheOrHe var:
func f(_ s: String) -> (String) -> String {
{ sheOrHe in "Is \(sheOrHe) \(s)?" }
}
let qFuncs = [f("great"), f("nice"), f("wonderful")]
qFuncs[0]("he") // "Is he great?"
qFuncs[0]("she") // "Is she great?"
Or without storing the functions:
func areThey(_ s: String) -> (String) -> String {
{ sheOrHe in "Is \(sheOrHe) \(s)?" }
}
let adjectives = ["great", "nice", "wonderful"]
areThey(adjectives[0])("he") // "Is he great?"
areThey(adjectives[0])("she") // "Is she great?"

Always getting the last item in a list using split

As of right now i retrieve the first item in my list that i generate by using split, but i also want the last item in a seperate function.
The problem ist the list is generated from an ocr and is never the same length or size.
is there a way in kotlin to always get the last item?
This is the code for getting the first :
fun String.firstLine(): String {
if (this.isEmpty()) return ""
return this.split("\n").get(0)
}
You can do something like this:
fun String.firstAndLast() = split("\n").let { it.first() to it.last() }
fun main() {
val s = "first line\nsecond line\nlast line"
val (first, last) = s.firstAndLast()
println(first)
println(last)
}
output is:
first line
last line
The fun will split, then use the resulting list and get only the first and last element, returning a Pair<String, String>, which can be easily deconstructed with val (first, last), then those values can be used separately.
Edit: for the penultimate as well, I don't think that a list might be a good idea. I personally would go with an object of sorts, or with a Triple.
Class way:
class NoNameIdeas(
val first: String = "",
val penultimate: String = "",
val last: String = ""
) {
companion object {
fun fromString(string: String): NoNameIdeas {
val l = string.split("\n")
val first = l.first()
return when (l.size) {
1 -> NoNameIdeas(first = first)
2 -> NoNameIdeas(first = first, penultimate = "what should this be?", last = l.last())
else -> NoNameIdeas(first = first, penultimate = l[l.lastIndex - 1], last = l.last())
}
}
}
}
fun main() {
val string = "first line\nsecond line\nsecond last\nlast line"
val result = NoNameIdeas.fromString(string)
println(result.first)
println(result.penultimate)
println(result.last)
}
As you can guess, I have no idea how to name this object. Another issues is, what should we do with the penultimate, if we only have 2 lines in total? It can be the same as the first, or it can be empty, or it can be the same as the last. That is your decision, it also might never happen, only you know what data you have.
Another way of doing it without a class, but by using a triple:
Triple way:
fun main() {
val string = "first line\nsecond line\nsecond last\nlast line"
val result = string.firstPenultimateAndLast()
println(result.first)
println(result.second)
println(result.third)
}
fun String.firstPenultimateAndLast(): Triple<String, String, String> {
val l = split("\n")
val first = l.first()
var penultimate = "" //same as the class, what happens when we only have 2 lines?
var last = l.first() //if we only have 1, then I guess that the first is the last as well, will get a new value otherwise
when (l.size) {
2 -> last = l.last()
else -> {
penultimate = l[lastIndex - 1]
last = l.last()
}
}
return Triple(first, penultimate, last)
}

How to check a generic type inside a Kotlin function?

I am using Kotlin to parse JSON. For example, I have this representation of a country: {"code":"US", "name":"United States of America"}. To produce a Country object from such a JSONObject, I have this function:
val produceCountry = fun (js: JSONObject) =
Country(js.getString("code"), js.getString("name"))
I can easily parse an array of Country with this function. Besides arrays of Country, however, I also have arrays of Cat, Car, Cart, CordlessPhone, etc. Each has their own produce* function transforming a JSONObject to a Kotlin object of that type. To generalize array parsing, I have this function:
fun <T> produceSetOf(array: JSONArray, element: (JSONObject) -> T): Set<T> {
val set = mutableSetOf<T>()
for (i in 0 until array.length())
set.add(element(array.getJSONObject(i)))
return set
}
So I can call produceSetOf(jsonArray, produceCountry) on encountering an array whose elements are of type Country. This works well on arrays of Cat, Car, Cart, CordlessPhone too.
Problem arises when I see an array of strings. Instead of array.getJSONObject(i), I have to use array.getString(i). In effect, I am thinking of introducing another parameterized type to the function above and have it make the call differently:
fun <S,T> produceSetOf(array: JSONArray, element: (S) -> T): Set<T> {
val set = mutableSetOf<T>()
for (i in 0 until array.length()) {
when (S) {
is String ->
set.add(element(array.getString(i)))
is JSONObject ->
set.add(element(array.getJSONObject(i)))
}
}
return set
}
Of course, Kotlin does not allow me to do that. Any suggestion how I could do that while maintaining the generality of produceSetOf() and without introducing another layer of abstraction (e.g. an element iterator, or a function transforming an index into String/JSONObject)?
Thank you.
Here is one possible solution using reified type parameters.
inline fun <reified S, T> produceSetOf(array: JsonArray, element: (S) -> T): Set<T> {
val set = mutableSetOf<T>()
for (i in 0 until array.size()) {
when (S::class) {
String::class -> set.add(element(array[i].string as S))
JsonObject::class -> set.add(element(array[i].obj as S))
}
}
return set
}
val stringArray = listOf("1", "2").toJsonArray()
val stringSet = produceSetOf<String, Int>(stringArray) { it.toInt() }
println(stringSet) // prints [1, 2]
val objArray = listOf(jsonObject("key" to "value"), jsonObject("key" to "other")).toJsonArray()
val objSet = produceSetOf<JsonObject, String>(objArray) { it["key"].string }
println(objSet) // print [value, other]
I used gson for the Json objects, since I didn't know where yours were from.
A possible shorter solution:
inline fun <reified S, T> produceSetOf(array: JsonArray, element: (S) -> T): Set<T> = array.map {
when (S::class) {
String::class -> element(it.string as S)
JsonObject::class -> element(it.obj as S)
else -> throw UnsupportedOperationException("${S::class.simpleName} is not supported")
}
}.toSet()

Closest-match string-array sorting in Swift

Using Swift4, I would like to sort a string-array according to the closest match to a given searchTerm. Important is to me that if the searchTerm can be found as an exact-match, then the returnArray should show this searchTerm upfront !
Example: Given the Array = ["Hello world", "Hello Jamaica", "Hello", "Family", "Hel"]
And the searchTerm = "Hello", the algorithm should return:
["Hello", "Hello world", "Hello Jamaica", "Hel", "Family"].
Approach 1:
I tried to use FuzzyMatching - and it somehow worked (i.e. it did sort the inputArray according to a given searchTerm, however it did not put the exact-matches upfront ! i.e. With FuzzyMatching I achieved a good sorting according to substring-matches and syntactic sorting. But it did not bring me the exact-matches upfront in the returnArray).
Approach 2:
Then I tried my own algorithm - (see code below). But if there are several strings in the array that all start with my searchTerm (i.e. have searchTerm as a prefix), then somehow my algo does not a good job.
static func bestMatchFilterdStringArray(inputArray: [String], searchTerm: String) -> [String] {
let matchingTerms = inputArray
.filter { $0.range(of: searchTerm, options: .caseInsensitive) != nil }
.sorted { ($0.hasPrefix(searchTerm) ? 0 : 1) < ($1.hasPrefix(searchTerm) ? 0 : 1) }
return matchingTerms
}
How is a "Closest-match string-array sorting" done in Swift4? Especially bringing me exact-matches upfront in the returnArray? Any help appreciated!
You can use Levenshtein distance score to compare your search term with every string in the array, and the one with the highest score will be the first term in your result array etc. Your result will be an array of strings sorted in descending order of the score.
Following extension to string can be used to get Levenshtein distance score. In this algorithm, higher the value, better the equality.
extension String {
func levenshteinDistanceScore(to string: String, ignoreCase: Bool = true, trimWhiteSpacesAndNewLines: Bool = true) -> Double {
var firstString = self
var secondString = string
if ignoreCase {
firstString = firstString.lowercased()
secondString = secondString.lowercased()
}
if trimWhiteSpacesAndNewLines {
firstString = firstString.trimmingCharacters(in: .whitespacesAndNewlines)
secondString = secondString.trimmingCharacters(in: .whitespacesAndNewlines)
}
let empty = [Int](repeating:0, count: secondString.count)
var last = [Int](0...secondString.count)
for (i, tLett) in firstString.enumerated() {
var cur = [i + 1] + empty
for (j, sLett) in secondString.enumerated() {
cur[j + 1] = tLett == sLett ? last[j] : Swift.min(last[j], last[j + 1], cur[j])+1
}
last = cur
}
// maximum string length between the two
let lowestScore = max(firstString.count, secondString.count)
if let validDistance = last.last {
return 1 - (Double(validDistance) / Double(lowestScore))
}
return 0.0
}
}

Equals method for data class in Kotlin

I have the following data class
data class PuzzleBoard(val board: IntArray) {
val dimension by lazy { Math.sqrt(board.size.toDouble()).toInt() }
}
I read that data classes in Kotlin get equals()/hashcode() method for free.
I instantiated two objects.
val board1 = PuzzleBoard(intArrayOf(1,2,3,4,5,6,7,8,0))
val board2 = PuzzleBoard(intArrayOf(1,2,3,4,5,6,7,8,0))
But still, the following statements return false.
board1 == board2
board1.equals(board2)
In Kotlin data classes equality check, arrays, just like other classes, are compared using equals(...), which compares the arrays references, not the content. This behavior is described here:
So, whenever you say
arr1 == arr2
DataClass(arr1) == DataClass(arr2)
...
you get the arrays compared through equals(), i.e. referentially.
Given that,
val arr1 = intArrayOf(1, 2, 3)
val arr2 = intArrayOf(1, 2, 3)
println(arr1 == arr2) // false is expected here
println(PuzzleBoard(arr1) == PuzzleBoard(arr2)) // false too
To override this and have the arrays compared structurally, you can implement equals(...)+hashCode() in your data class using Arrays.equals(...) and Arrays.hashCode(...):
override fun equals(other: Any?): Boolean{
if (this === other) return true
if (other?.javaClass != javaClass) return false
other as PuzzleBoard
if (!Arrays.equals(board, other.board)) return false
return true
}
override fun hashCode(): Int{
return Arrays.hashCode(board)
}
This code is what IntelliJ IDEA can automatically generate for non-data classes.
Another solution is to use List<Int> instead of IntArray. Lists are compared structurally, so that you won't need to override anything.
Kotlin implementation:
override fun equals(other: Any?): Boolean {
when (other) {
is User -> {
return this.userId == other.userId &&
this.userName == other.userName
}
else -> return false
}
}
For Data classes in Kotlin, hashcode() method will generate and return the same integer if parameters values are same for both objects.
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println(user.hashCode().equals(secondUser.hashCode()))
println(user.hashCode().equals(thirdUser.hashCode()))
Running this code will return True and False as when we created secondUser object we have passed same argument as object user, so hashCode() integer generated for both of them will be same.
also if you will check this:
println(user.equals(thirdUser))
It will return false.
As per hashCode() method docs
open fun hashCode(): Int (source)
Returns a hash code value for the object. The general contract of
hashCode is:
Whenever it is invoked on the same object more than once, the hashCode
method must consistently return the same integer, provided no
information used in equals comparisons on the object is modified.
If two objects are equal according to the equals() method, then calling
the hashCode method on each of the two objects must produce the same
integer result.
For more details see this discussion here
In Kotlin, equals() behaves differently between List and Array, as you can see from code below:
val list1 = listOf(1, 2, 3)
val list2 = listOf(1, 2, 3)
val array1 = arrayOf(1, 2, 3)
val array2 = arrayOf(1, 2, 3)
//Side note: using a==b is the same as a.equals(b)
val areListsEqual = list1 == list2// true
val areArraysEqual = array1 == array2// false
List.equals() checks whether the two lists have the same size and contain the same elements in the same order.
Array.equals() simply does an instance reference check. Since we created two arrays, they point to different objects in memory, thus not considered equal.
Since Kotlin 1.1, to achieve the same behavior as with List, you can use Array.contentEquals().
Source: Array.contentEquals() docs ;
List.equals() docs
The board field in the PuzzleBoard class is an IntArray, when compiled it is turned into a primitive integer array. Individual array elements are never compared when checking the equality of primitive integer arrays. So calling equals on int array returns false as they are pointing to different objects. Eventually, this results in getting false in the equals() method, even though array elements are the same.
Byte code check
Looking at the decompiled java byte code, the Kotlin compiler generates some functions of data classes for us.
This includes,
copy() function
toString() function - takes form ClassName(var1=val1, var2=val2, ...)
hashCode() function
equals() function
Hash code is generated by adding the hash code of individual variables and multiplying by 31. The reason for multiplying is that it can be replaced with the bitwise operator and according to experimental results, 31 and other numbers like 33, 37, 39, 41, etc. gave fever clashes when multiplied.
Take a look at decompiled java byte code of the Kotlin class PuzzleBoard which reveals the secrets of data classes.
#Metadata(
mv = {1, 7, 1},
k = 1,
d1 = {"\u0000(\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u0015\n\u0002\b\u0004\n\u0002\u0010\b\n\u0002\b\u0007\n\u0002\u0010\u000b\n\u0002\b\u0003\n\u0002\u0010\u000e\n\u0000\b\u0086\b\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\t\u0010\r\u001a\u00020\u0003HÆ\u0003J\u0013\u0010\u000e\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u0003HÆ\u0001J\u0013\u0010\u000f\u001a\u00020\u00102\b\u0010\u0011\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0012\u001a\u00020\bHÖ\u0001J\t\u0010\u0013\u001a\u00020\u0014HÖ\u0001R\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006R\u001b\u0010\u0007\u001a\u00020\b8FX\u0086\u0084\u0002¢\u0006\f\n\u0004\b\u000b\u0010\f\u001a\u0004\b\t\u0010\n¨\u0006\u0015"},
d2 = {"Lcom/aureusapps/androidpagingbasics/data/PuzzleBoard;", "", "board", "", "([I)V", "getBoard", "()[I", "dimension", "", "getDimension", "()I", "dimension$delegate", "Lkotlin/Lazy;", "component1", "copy", "equals", "", "other", "hashCode", "toString", "", "androidpagingbasics_debug"}
)
public final class PuzzleBoard {
#NotNull
private final Lazy dimension$delegate;
#NotNull
private final int[] board;
public final int getDimension() {
Lazy var1 = this.dimension$delegate;
Object var3 = null;
return ((Number)var1.getValue()).intValue();
}
#NotNull
public final int[] getBoard() {
return this.board;
}
public PuzzleBoard(#NotNull int[] board) {
Intrinsics.checkNotNullParameter(board, "board");
super();
this.board = board;
this.dimension$delegate = LazyKt.lazy((Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
public final int invoke() {
return (int)Math.sqrt((double)PuzzleBoard.this.getBoard().length);
}
}));
}
#NotNull
public final int[] component1() {
return this.board;
}
#NotNull
public final PuzzleBoard copy(#NotNull int[] board) {
Intrinsics.checkNotNullParameter(board, "board");
return new PuzzleBoard(board);
}
// $FF: synthetic method
public static PuzzleBoard copy$default(PuzzleBoard var0, int[] var1, int var2, Object var3) {
if ((var2 & 1) != 0) {
var1 = var0.board;
}
return var0.copy(var1);
}
#NotNull
public String toString() {
return "PuzzleBoard(board=" + Arrays.toString(this.board) + ")";
}
public int hashCode() {
int[] var10000 = this.board;
return var10000 != null ? Arrays.hashCode(var10000) : 0;
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof PuzzleBoard) {
PuzzleBoard var2 = (PuzzleBoard)var1;
if (Intrinsics.areEqual(this.board, var2.board)) {
return true;
}
}
return false;
} else {
return true;
}
}
}

Resources