how to search for series of strings in a sentence in kotlin - kotlin

I have an ArrayList of items. Each item has long strings for example
("The cat is in the hat","It's warm outside","It's cold outside")
what I am trying to do is search for a series of strings for example "It's outside" in any given order in the ArrayList above and it should find 2 of them.
This is what I tried:
fun clickItem(criteria: String) {
productList = productListAll.filter {it: Data
it.title.contains(criteria, ignoreCase = true)
}
} as ArrayList<Data>
This works fine when the words I am looking for are in sequence. However, I am trying to get strings in any given order. Does anyone know how to accomplish that?

We can do this by splitting title and criteria by whitespaces to create a set of words. Then we use containsAll() to check if title contains all of words from criteria. Additionally, we need to convert both of them to lowercase (or uppercase), so the search will be case-insensitive:
private val whitespace = Regex("\\s+")
fun clickItem(criteria: String): List<Data> {
val criteriaWords = criteria.lowercase().split(whitespace).toSet()
return productListAll.filter {
it.title.lowercase().split(whitespace).containsAll(criteriaWords)
}
}
Note that searching through text is not that trivial, so simple solutions will be always limited. For example, we won't find "it's" when searching for "it is", etc.

Related

how to read mutliple lines of string into one variable using readln() in kotlin?

example:
a variable
val str = readln().replace("[^A-Za-z0-9 ] \\s+".toRegex(),"").trim()
should read multiple lines of input value, input value will be like this
heading
----------
topic1
topic2
or like this
heading
-------
a) topic1
b) topic2
input may contain special characters or tabs or spaces we need to remove them also
I don't know what your Regex is trying to do, but that's not really your question.
How do you know when the user has finished their input - a special word or an empty line?
Assuming an empty line, here's how you can get all the content
println("Enter something:")
var lines = ""
do {
val line = readLine()
lines += "${clean(line)}\n"
} while (!line.isNullOrBlank())
println("User input:\n$lines")
private fun clean(line: String?): String? {
return line?.replace("[^A-Za-z0-9 ] \\s+".toRegex(),"")?.trim()
}

Combining Two List in Kotlin with Index

There is a data class as fruits.
data class Fruits(
val code: String, //Unique
val name: String
)
The base list indexed items with boolean variable is as below.
val indexList: MutableList<Boolean> = MutableList(baseFruitList.size) { false }
Now the Favourite Indexed list is as below
val favList: MutableList<Boolean> = MutableList(favFruitList.size) { true}
I want a combined full list which basically has the fav item indicated as true.
Ex:
baseFruitList = {[FT1,apple],[FT2,grapes],[FT3,banana],[FT4,mango],[FT5,pears]}
favList = {[FT2,grapes],[FT4,mango]}
The final index list should have
finalIndexed = {false,true,false,true,false}
How can we achieve in Kotlin, without iterating through each element.
You can do
val finalIndexed = baseFruitList.map { it in favList }
assuming, like #Tenfour04 is asking, that name is guaranteed to be a specific value (including matching case) for a specific code (since that combination is how a data class matches another, e.g. for checking if it's in another list)
If you can't guarantee that, this is safer:
val finalIndexed = baseFruitList.map { fruit ->
favList.any { fav.code == fruit.code }
}
but here you have to iterate over all the favs (at least until you find a match) looking to see if one has the code.
But really, if code is the unique identifier here, why not just store those in your favList?
favList = listOf("FT2", "FT4") // or a Set would be more efficient, and more correct!
val finalIndexed = baseFruitList.map { it.code in favList }
I don't know what you mean about "without iterating through each element" - if you mean without an explicit indexed for loop, then you can use these simple functions like I have here. But there's always some amount of iteration involved. Sets are always an option to help you minimise that

How to filter a list by using the ids of another list?

I have a list of ids. I want to filter my list and only keep the values in that list that match the id.
fun filterHelper(ids: List<Int>, list: List<People>) {
list.filter { ids.contains(it.id) }
}
But this is very inefficient. It is essentially traversing the list O(n^2). Does Kotlin let me do better?
I asked a similar question about slicing maps recently. The answer is that there is no good built-in function, but you can work around by using a Set instead of a List for your ids, which gets you O(1) lookup time for the comparisons, so O(n) in total.
data class People(val id: Int)
fun main() {
val people = listOf(People(1), People(2), People(3), People(4))
val ids = setOf(2, 4)
val filtered = people.filter { it.id in ids }
println(filtered)
}
Output:
[People(id=2), People(id=4)]
It's worth mentioning that if you already have a list, you can easily convert to a set with:
list.toSet()

Removing unncecessary parts from String

I'm consuming a client that I cannot change and it sends me data that looks like that:
"BOOKING - PAID (price.amount=70, price.currency=EUR)"
and I would like to retrieve from that only 70 EUR
What is the best way to do such thing in kotlin? I didn't find any removeAll("", "", ...) functions for String, only replace but would have to chain them to remove both price.amount and price.currency.
EDIT:
Need to get BOOKING - PAID (70 EUR) actually, forgot about that part.
Thinking about it and as you updated your question to really remove only a part from the string, here are some approaches for removing several strings from a given string:
Using regex:
input.replace("""(price\.(amount|currency)=|,)""".toRegex(), "")
Using a list of strings to remove:
sequenceOf(input, "price.amount=", ",", "price.currency=")
.reduce { acc, rm -> acc.replace(rm, "") }
// alternatively using var:
var input = TODO()
sequenceOf("price.amount=", ",", "price.currency=")
.forEach { input = input.replace(it, "") }
Still: most of the time I would rather take the other route: extracting the information you require and just print that, as also Baptiste has shown in his answer. Otherwise you may start to expose answers of that service you didn't want to expose in the first place.
This sounds like a job for regular expressions!
fun main(args: Array<String>) {
val str = "BOOKING - PAID (price.amount=70, price.currency=EUR)"
// The expressions between parentheses will map to groups[1], groups[2] and groups[3] respectively
val reg = Regex("""(.*) \(price\.amount=([0-9]+), price\.currency=([A-Z]+)\)""")
// Apply the regular expression on the string
val results = reg.find(str)
results?.groupValues?.let { groups ->
// If results and groupValues aren't null, we've got our values!
val type = groups[1]
val price = groups[2]
val currency = groups[3]
println("$type ($price $currency)") // BOOKING - PAID (70 EUR)
}
// Or as suggested by #Roland:
results?.destructured?.let { (type, price, currency) ->
println("$type ($price $currency)") // BOOKING - PAID (70 EUR)
}
}
Regular expressions allow you to take a string as entry, and find a pattern in it. They're quite used in all languages, you can find more info about them all over the place.
EDIT: edited for the updated question. I chose to treat "BOOKING - PAID" as a single string, but there's an infinite number of ways to do it, depending on your granularity needs; and honestly, at that point a regex might be a bit overkill. :)
Without regex by string manipulation and assuming that this is the pattern:
fun main(args: Array <String> ) {
val str = "BOOKING - PAID (price.amount=70, price.currency=EUR)"
val type = str.substringBefore("(").trim()
val price = str.substringBeforeLast(",").substringAfter("=")
val currency = str.substringAfterLast("=").substringBefore(")")
val result = "$type ($price $currency)"
println(result)
}
will print
BOOKING - PAID (70 EUR)
Edit: I use str.substringBeforeLast(",") to get the price, in case , could be used as a delimeter for decimal part in the number

Comparing and removing object from ArrayLists using Java 8

My apologies if this is a simple basic info that I should be knowing. This is the first time I am trying to use Java 8 streams and other features.
I have two ArrayLists containing same type of objects. Let's say list1 and list2. Let's say the lists has Person objects with a property "employeeId".
The scenario is that I need to merge these lists. However, list2 may have some objects that are same as in list1. So I am trying to remove the objects from list2 that are same as in list1 and get a result list that then I can merge in list1.
I am trying to do this with Java 8 removeIf() and stream() features. Following is my code:
public List<PersonDto> removeDuplicates(List<PersonDto> list1, List<PersonDto> list2) {
List<PersonDto> filteredList = list2.removeIf(list2Obj -> {
list1.stream()
.anyMatch( list1Obj -> (list1Obj.getEmployeeId() == list2Obj.getEmployeeId()) );
} );
}
The above code is giving compile error as below:
The method removeIf(Predicate) in the type Collection is not applicable for the arguments (( list2Obj) -> {})
So I changed the list2Obj at the start of "removeIf()" to (<PersonDto> list2Obj) as below:
public List<PersonDto> removeDuplicates(List<PersonDto> list1, List<PersonDto> list2) {
List<PersonDto> filteredList = list2.removeIf((<PersonDto> list2Obj) -> {
list1.stream()
.anyMatch( list1Obj -> (list1Obj.getEmployeeId() == list2Obj.getEmployeeId()) );
} );
}
This gives me an error as below:
Syntax error on token "<", delete this token for the '<' in (<PersonDto> list2Obj) and Syntax error on token(s), misplaced construct(s) for the part from '-> {'
I am at loss on what I really need to do to make it work.
Would appreciate if somebody can please help me resolve this issue.
I've simplified your function just a little bit to make it more readable:
public static List<PersonDto> removeDuplicates(List<PersonDto> left, List<PersonDto> right) {
left.removeIf(p -> {
return right.stream().anyMatch(x -> (p.getEmployeeId() == x.getEmployeeId()));
});
return left;
}
Also notice that you are modifying the left parameter, you are not creating a new List.
You could also use: left.removeAll(right), but you need equals and hashcode for that and it seems you don't have them; or they are based on something else than employeeId.
Another option would be to collect those lists to a TreeSet and use removeAll:
TreeSet<PersonDto> leftTree = left.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PersonDto::getEmployeeId))));
TreeSet<PersonDto> rightTree = right.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(PersonDto::getEmployeeId))));
leftTree.removeAll(rightTree);
I understand you are trying to merge both lists without duplicating the elements that belong to the intersection. There are many ways to do this. One is the way you've tried, i.e. remove elements from one list that belong to the other, then merge. And this, in turn, can be done in several ways.
One of these ways would be to keep the employee ids of one list in a HashSet and then use removeIf on the other list, with a predicate that checks whether each element has an employee id that is contained in the set. This is better than using anyMatch on the second list for each element of the first list, because HashSet.contains runs in O(1) amortized time. Here's a sketch of the solution:
// Determine larger and smaller lists
boolean list1Smaller = list1.size() < list2.size();
List<PersonDto> smallerList = list1Smaller ? list1 : list2;
List<PersonDto> largerList = list1Smaller ? list2 : list1;
// Create a Set with the employee ids of the larger list
// Assuming employee ids are long
Set<Long> largerSet = largerList.stream()
.map(PersonDto::getEmployeeId)
.collect(Collectors.toSet());
// Now remove elements from the smaller list
smallerList.removeIf(dto -> largerSet.contains(dto.getEmployeeId()));
The logic behind this is that HashSet.contains will take the same time for both a large and a small set, because it runs in O(1) amortized time. However, traversing a list and removing elements from it will be faster on smaller lists.
Then, you are ready to merge both lists:
largerList.addAll(smallerList);