Filtering multiple times on one dictionary - filter

I currently run this code:
searchterm = "test"
results = resultsArray.filter { $0.description.contains (searchterm!) }
My question is how do I search in company_name or place or any other field in my model and add it to the results.
Do I need to use filters together and then append the results to a new variable instance of my model?
EDIT:
If "test" is in company_name, place and description. I want all three results returned. However, if "test" is only in place, I need only place to be returned.
EDIT2:
This is an example of my model return. Is this a dictionary or an array? I'm sorry I dont 100% percent know the difference. I know ' "this": is ' what a dictionary looks like, however because there were [] brackets around them, I thought that made it an array...
struct GraphData {
var description: String
var company_name: String
var places: String
init(description: String, company_name: String, places: String){
self.description = description
self.company_name = company_name
self.places = places
}
func toAnyObject() -> Any {
print("return")
return [
"description": description,
"company_name": company_name,
"places": places,
]
}

The easiest way to do this would be to create a custom contains method in your model which can you can use to match the search term against any property in the model:
class YourModel {
var company_name: String
var description: String
var place: String
// ...
func contains(_ searchTerm: String) -> Bool {
return self.company_name.contains(searchTerm)
|| self.description.contains(searchTerm)
|| self.place.contains(searchTerm)
}
}
You can then simply filter using your custom method:
let searchTerm = "test"
let results = resultsArray.filter { $0.contains(searchTerm) }

Is this resultsArray a dictionary or an array?
You can do something like this
let searchTerm = "test"
let filter = resultsArray.filter{ $0.company_name!.contains(searchTerm) || $0.place!.contains(searchTerm) }
Edit
class TestClass: NSObject {
var place: String?
var company_name: String?
func contain(searchTerm: String) -> [String] {
var result = [String]()
if let placeVal = place, placeVal.contains(searchTerm) {
result.append(placeVal)
}
if let companyVal = company_name, companyVal.contains(searchTerm) {
result.append(companyVal)
}
return result
}
}
let searchTerm = "test"
let filter = resultsArray.map { $0.contain(searchTerm: searchTerm) }

Related

How to create a sorted merged list from two diffrent ArrayList of Objects based on a common value field in Kotlin?

I have two ArrayLists of different Data classes as given below:
class Record{
var id: Long = 0
var RecordId: Int = 0
var Record: String? = null
var title: String? = null
var description: String? = null
var longDate: Long = 0
}
class Type{
var id: Long = 0
var typeId: Int = 0
var subTypeId: Int = 0
var typeString: String? = null
var longDate: Long = 0
}
var recordsList: ArrayList<Record>
var typesList: ArrayList<Type>
Now, I want a merged list of these two which will be sorted based on a common field in both the Objects i.e. longDate. I have tried .associate , sortedBy, sortedWith(compareBy<>) etc. but could not achieve the desired result.
Here, also there is one point to note is that while comparing the two lists it is possible that one on them may be empty.
This will produce a List<Any> with all items sorted by longDate:
(recordsList + typesList)
.sortedBy {
when (it) {
is Record -> it.longDate
is Type -> it.longDate
else -> error("")
}
}
Or you might consider creating an interface that has val longDate: Long that both of these classes implement. Then you wouldn't need the when expression, and your List would be of the type of the interface.
Something like this should work, but I personally think that it is quite the code smell. There is no guarantee that Record.longDate is truly the same type as Type.longDate (we know that it is, since we create the model, but the compiler would never know).
val result = (recordsList + typesList).sortedBy {
when(it){
is Record -> it.longDate
is Type -> it.longDate
else -> error("incompatible list element $it")
}
}
And it would work something like this: (I've removed some parameters from the models as they don't really count here)
fun main() {
val recordsList = listOf(Record().apply { longDate = 5 }, Record().apply { longDate = 3})
val typesList = listOf(Type().apply { longDate = 3 }, Type().apply { longDate = 2 })
val result = (recordsList + typesList).sortedBy {
when(it){
is Record -> it.longDate
is Type -> it.longDate
else -> error("incompatible list element $it")
}
}
result.forEach{
println(it.toString())
}
}
class Record{
var longDate: Long = 0
override fun toString(): String {
return "Record(longDate=$longDate)"
}
}
class Type{
var longDate: Long = 0
override fun toString(): String {
return "Type(longDate=$longDate)"
}
}
This will output:
Type(longDate=2)
Record(longDate=3)
Type(longDate=3)
Record(longDate=5)
Doing it in a more generic way, so that you can create a fun where you state which property to be used from each object type would most likely use reflection, which I'd avoid at all costs.
So I would definitely consider if one object can inherit the other, or create an interface, or anything else.
I'll end with 2 questions: why no constructors? why ArrayList and not list?

Filtering String from Firestore database sends an error on SwiftUI

I'm working on implementing a search for a movies database stored in firestore using SwiftUI. I've tried doing filtering the movie names by the entered text as follows:
#ObservedObject var movies = getMoviesData()
...
ForEach(self.movies.datas) { movies in
ForEach(movies.title.filter({"\($0)".contains(searchText.lowercased()) || searchText.isEmpty})) { item in
if let url = URL(string: movies.img) {
AnimatedImage(url: url)
.resizable()
.frame(width: bounds.size.width / 2 - 0.6, height: bounds.size.height / 2 - 0.2)
}
}
.animation(.spring())
}
...
struct movies : Identifiable {
var id: String
var title: String
var img: String
var video: String
var description: String
var genre: String
}
class getMoviesData : ObservableObject{
#Published var datas = [movies]()
private var db = Firestore.firestore()
func fetchData(){
db.collection("movies").addSnapshotListener{ (querySnapshot, error) in
guard let mov = querySnapshot?.documents else{
print("No movies")
return
}
self.datas = mov.map{(queryDocumentSnapshot) -> movies in
let data = queryDocumentSnapshot.data()
let id = data["id"] as? String ?? ""
let title = data["title"] as? String ?? ""
let img = data["img"] as? String ?? ""
let video = data["video"] as? String ?? ""
let description = data["description"] as? String ?? ""
let genre = data["genre"] as? String ?? ""
return movies(id: id, title: title, img: img, video: video, description: description, genre: genre)
}
}
}
}
However, I'm receiving the following error on the second ForEach statement:
Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'String.Element' (aka 'Character') conform to 'Identifiable'
Movies.title represents a String output of each movie identified in the ForEach statement. How should I filter movies.title against the provided search text without invoking this error?
I am little confused about your use of variable names.
ForEach(movies.title.filter({"\($0)".contains(searchText.lowercased()) || searchText.isEmpty})) { item in
}
}
In the above code movies.title is very misleading. Is there a title property on movies array. If you need to filter, you can perhaps check out the sample code below:
struct Movie {
let title: String
}
let movies = [Movie(title: "Spiderman"), Movie(title: "Batman"), Movie(title: "Superman")]
let searchWord = "batman"
let filteredMovies = movies.filter { movie in
return movie.title.lowercased().contains(searchWord.lowercased())
}
print(filteredMovies)

Replacing for loops for searching list in kotlin

I am trying to convert my code as clean as possible using the Kotlin's built-in functions. I have done some part of the code using for loops. But I want to know the efficient built-in functions to be used for this application
I have two array lists accounts and cards.
My goal is to search a specific card with the help of its card-number, in the array list named cards.
Then I have to validate the pin. If the pin is correct, by getting that gift card's customerId I have to search the account in the array list named accounts. Then I have to update the balance of the account.
These are the class which I have used
class Account{
constructor( )
var id : String = generateAccountNumber()
var name: String? = null
set(name) = if (name != null) field = name.toUpperCase() else { field = "Unknown User"; println("invalid details\nAccount is not Created");}
var balance : Double = 0.0
set(balance) = if (balance >= 0) field = balance else { field = 0.0 }
constructor(id: String = generateAccountNumber(), name: String?,balance: Double) {
this.id = id
this.balance = balance
this.name = name
}
}
class GiftCard {
constructor( )
var cardNumber : String = generateCardNumber()
var pin: String? = null
set(pin) = if (pin != null) field = pin else { field = "Unknown User"; println("Please set the pin\nCard is not Created");}
var customerId : String = ""
set(customerId) = if (customerId != "") field = customerId else { field = "" }
var cardBalance : Double = 0.0
set(cardBalance) = if (cardBalance > 0) field = cardBalance else { field = 0.0; println("Card is created with zero balance\nPlease deposit") }
var status = Status.ACTIVE
constructor(cardNumber: String = generateCardNumber(),
pin: String,
customerId: String,
cardBalance: Double = 0.0,
status: Status = Status.ACTIVE){
this.cardNumber = cardNumber
this.pin = pin
this.customerId = customerId
this.cardBalance = cardBalance
this.status = status
}
}
This is the part of code, I have to be changed :
override fun closeCard(cardNumber: String, pin: String): Pair<Boolean, Boolean> {
for (giftcard in giftcards) {
if (giftcard.cardNumber == cardNumber) {
if (giftcard.pin == pin) {
giftcard.status = Status.CLOSED
for (account in accounts)
account.balance = account.balance + giftcard.cardBalance
giftcard.cardBalance = 0.0
return Pair(true,true)
}
\\invalid pin
return Pair(true,false)
}
}
\\card is not present
return Pair(false,false)
}
Both classes are not very idiomatic. The primary constructor of a Kotlin class is implicit and does not need to be defined, however, you explicitly define a constructor and thus you add another one that is empty.
// good
class C
// bad
class C {
constructor()
}
Going further, Kotlin has named arguments and default values, so make use of them.
class Account(
val id: String = generateAccountNumber(),
val name: String = "Unknown User",
val balance: Double = 0.0
)
Double is a very bad choice for basically anything due to its shortcomings, see for instance https://www.floating-point-gui.de/ Choosing Int, Long, heck even BigDecimal would be better. It also seems that you don’t want the balance to ever go beneath zero, in that case consider UInt and ULong.
Last but not least is the mutability of your class. This can make sense but it also might be dangerous. It is up to you to decide upon your needs and requirements.
enum class Status {
CLOSED
}
#ExperimentalUnsignedTypes
class Account(private var _balance: UInt) {
val balance get() = _balance
operator fun plusAssign(other: UInt) {
_balance += other
}
}
#ExperimentalUnsignedTypes
class GiftCard(
val number: String,
val pin: String,
private var _status: Status,
private var _balance: UInt
) {
val status get() = _status
val balance get() = _balance
fun close() {
_status = Status.CLOSED
_balance = 0u
}
}
#ExperimentalUnsignedTypes
class Main(val accounts: List<Account>, val giftCards: List<GiftCard>) {
fun closeCard(cardNumber: String, pin: String) =
giftCards.find { it.number == cardNumber }?.let {
(it.pin == pin).andAlso {
accounts.forEach { a -> a += it.balance }
it.close()
}
}
}
inline fun Boolean.andAlso(action: () -> Unit): Boolean {
if (this) action()
return this
}
We change the return type from Pair<Boolean, Boolean> to a more idiomatic Boolean? where Null means that we did not find anything (literally the true meaning of Null), false that the PIN did not match, and true that the gift card was closed. We are not creating a pair anymore and thus avoid the additional object allocation.
The Boolean.andAlso() is a handy extension function that I generally keep handy, it is like Any.also() from Kotlin’s STD but only executes the action if the Boolean is actually true.
There's probably a million different ways to do this, but here's one that at least has some language features I feel are worthy to share:
fun closeCard(cardNumber: String, pin: String): Pair<Boolean, Boolean> {
val giftCard = giftcards.find { it.cardNumber == cardNumber }
?: return Pair(false, false)
return if (giftCard.pin == pin) {
giftCard.status = Status.CLOSED
accounts.forEach {
it.balance += giftCard.cardBalance
}
Pair(true, true)
} else
Pair(true, false)
}
The first thing to notice if the Elvis operator - ?: - which evaluates the right side of the expression if the left side is null. In this case, if find returns null, which is equivalent to not finding a card number that matches the desired one, we'll immediately return Pair(false, false). This is the last step in your code.
From there one it's pretty straight forward. If the pins match, you loop through the accounts list with a forEach and close the card. If the pins don't match, then we'll go straight to the else branch. In kotlin, if can be used as an expression, therefore we can simply put the return statement before the if and let it return the result of the last expression on each branch.
PS: I won't say this is more efficient than your way. It's just one way that uses built-in functions - find and forEach - like you asked, as well as other language features.
PPS: I would highly recommend to try and find another way to update the lists without mutating the objects. I don't know your use cases, but this doesn't feel too thread-safe. I didn't post any solution for this, because it's outside the scope of this question.

How to search for the name and surname using Swift for OS X and avoid duplicate results

I am trying to create a code in Swift for OS X and one of its function is to searches for Address Book entries.
The code bellow finds the entries and works fine with one search term, lets say person's name John. However, if second search term used: i.e. name and surname "John Doe" (space separated) it returns all the matches for the name "John" and all the matches for the surname "Doe".
How to force the code to search for the rest of the string i.e. surname only in the Address Book entries that already matched first part of the sting entered i.e. name?
Searching for John Dow I would like to get John Doe's Address Book entry only and not a list of "John Doe, John Roe, John Moe, Andrew Doe" etc?
Sorry for my English
let addressBook = ABAddressBook.sharedAddressBook()
var dataArray:[NSDictionary] = [["personCell": "", "emailCell": ""]]
var foundPeople = 0
var personNo = 0
var searchString = "John Doe"
func searchAB() -> NSArray {
var mySearchWord = searchString.componentsSeparatedByString(" ")
for word in mySearchWord {
var searchWord = mySearchWord.removeAtIndex(mySearchWord.endIndex.predecessor())
let comparison: ABSearchConjunction = CFIndex(kABContainsSubStringCaseInsensitive.value)
let firstNameSearch = ABPerson.searchElementForProperty(kABFirstNameProperty,
label: nil,
key: nil,
value: searchWord,
comparison: comparison)
let middleNameSearch = ABPerson.searchElementForProperty(kABMiddleNameProperty,
label: nil,
key: nil,
value: searchWord,
comparison: comparison)
let lastNameSearch = ABPerson.searchElementForProperty(kABLastNameProperty,
label: nil,
key: nil,
value: searchWord,
comparison: comparison)
let comparisons = [firstNameSearch, middleNameSearch, lastNameSearch]
let orComparison = ABSearchElement(forConjunction: CFIndex(kABSearchOr.value), children: comparisons)
let found = addressBook.recordsMatchingSearchElement(orComparison) as! [ABRecord]
for person in found {
let firstName = person.valueForProperty(kABFirstNameProperty) as! String? ?? ""
let middleName = person.valueForProperty(kABMiddleNameProperty) as! String? ?? ""
let lastName = person.valueForProperty(kABLastNameProperty) as! String? ?? ""
let emailsProperty = person.valueForProperty(kABEmailProperty) as! ABMultiValue?
foundPeople++
var tmpFirstName = firstName
var booleanFName = tmpFirstName.isEqualToString(firstName)
if let emails = emailsProperty {
for i in 0..<emails.count() {
let email = emails.valueAtIndex(i) as! String
var emailType = ABLocalizedPropertyOrLabel(emails.labelAtIndex(i)) //telefono numerio tipas
if (booleanFName) {
personNo++
if (personNo == 1) { // first person
dataArray = [["personCell": "\(firstName) \(middleName) \(lastName)", "emailCell": "\(email) (\(emailType))"]]
} else {
dataArray += [["personCell": "\(firstName) \(middleName) \(lastName)", "emailCell": "\(email) (\(emailType))"]]
} // end if personNo
println("\n• \(personNo) \(firstName) \(middleName) \(lastName)")
println("\t\t\(email) (\(emailType))")
booleanFName = false
} else {
dataArray += [["emailCell": "\(email) (\(emailType))"]]
println("\t\t\(email) (\(emailType))")
} // end booleanFName
} // end for i
} // end if let emails
} // end for person in found
} // end for word
return dataArray;
} // end func

Dictionary does not have a member named filter - but it should, shouldn't it?

I'm trying to get code from github for auto-completion to work, but am stuck with an error on line 6 (data.filter) that Dictionary does not have a member named filter. But everything I read in the documentation suggests dictionaries should have a filter method. I've tried every possible combination of unwrapping, self, etc, but the compiler then registers these changes as the error.
Obviously something is going on that I do not understand - any guidance is appreciated.
var data = Dictionary<String, AnyObject>()
func applyFilterWithSearchQuery(filter : String) -> Dictionary<String, AnyObject>
{
var lower = (filter as NSString).lowercaseString
if (data.count > 0) {
var filteredData = data.filter ({
if let match : AnyObject = $0["DisplayText"]{
return (match as NSString).lowercaseString.hasPrefix((filter as NSString).lowercaseString)
}
else{
return false
}
})
}
return filteredData
}
Never heard of dictionary filters - where have you read about that? Arrays do have a filter method, but not dictionaries. What you can do is filter keys or values (accessible via the respective dictionary properties).
You can implement a custom filter on a dictionary with the following code:
var filtered = dict.keys.filter { $0.hasPrefix("t") }.map { (key: $0, value: dict[$0]) }
var newDict = [String : AnyObject]()
for element in filtered {
newDict[element.key] = element.value
}
which filter keys, then maps each key to a (key, value) tuple, then add each tuple to a new dictionary.
Note that to filter keys I used hasPrefix("t") - replace that with something more appropriate to your case
var data = Dictionary<String, AnyObject>()
func applyFilterWithSearchQuery(filter: String) -> [String : AnyObject] {
var lower = filter.lowercaseString
var filteredData = [String : AnyObject]()
if (!data.isEmpty) {
Swift.filter(data) {
(key, value) -> Bool in
if key == "DisplayText" {
filteredData[key] = (value as? String)?.lowercaseString.hasPrefix(lower)
}
return false
}
/* use map function works too
map(data) {
(key, value) -> Void in
if key == "DisplayText" {
filteredData[key] = (value as? String)?.lowercaseString.hasPrefix(lower)
}
}
*/
}
return filteredData
}

Resources