Multiple search in string - swift 4+ - ios

I have an simple problem with my code..
I wan't to make search like in facebook messenger - what do I mean?
I have multiple strings converted to one string without spaces like
"my string llama" -> "mystringllama"
I want to make searcher to search by two or more words like:
when I type "my ama" I want to search every record which have those characters..
Can someone helps?
My code:
func searchFor(text: String) {
if text == "" || text.count == 0 {
loadPricesFromDb()
}
else {
let realm = try! Realm()
self.items = []
let prices = realm.objects(Price.self)
let results = prices.filter({($0.MultipleSearchString?.lowercased().contains(text.rangeOfCharacter(from: text)))!
})
self.items.append(contentsOf: results)
self.tableView.reloadData()
}
}
And I just want to make it good. Now I don't know what I need to use as my filter to search it good.. :/

You may find a database command to get this kind of search. In swift, it's easy to construct such a predicate like the following if I understand your requirement right.
let multipleSearchString = "my stg l la ma"
let texts = ["mystringlladm1a", "mystr2ingllama", "mystri2ngllama", "mys3ringllama"]
let key = multipleSearchString.compactMap{ $0 == " " ? nil : String($0) + "*"}.joined()
let predicate = NSPredicate(format: "SELF like[c] %#", argumentArray: [key])
print (texts.filter{predicate.evaluate(with: $0)})
//["mystringlladm1a", "mystr2ingllama", "mystri2ngllama"]
In your added case, a computed property can be added to achieve your goal:
// extension Price{
// var lowerCasedMultipleSearchString: String{
// if let string = self.MultipleSearchString?.lowercased(){
// return string
// }
// return ""
// }
// }
// func searchTextInPrices(prices: Results<Price>, text: String) -> //Results<Price> {
// let key = text.compactMap{ $0 == " " ? nil : String($0) + "*"}.joined()
// let predicate = NSPredicate(format: "SELF like[c] %#", argumentArray: [key])
// return prices.filter{predicate.evaluate(with: $0.lowerCasedMultipleSearchString)}
// }
func searchFor(text: String) {
if text == "" || text.count == 0 {
loadPricesFromDb()
}
else {
let realm = try! Realm()
self.items = []
let prices = realm.objects(Price.self)
//The changed codes here.
let key = text.compactMap{ $0 == " " ? nil : String($0) + "*"}.joined()
let predicate = NSPredicate(format: "SELF like[c] %#", argumentArray: [key])
let results = prices.filter{predicate.evaluate(with: ($0.MultipleSearchString?.lowercased())!)}
//second edition:
var result = prices
var length : Int = 0
repeat{
let key = text[...(text.index(text.startIndex, offsetBy: length))].compactMap{ $0 == " " ? nil : String($0) + "*"}.joined()
let predicate = NSPredicate(format: "SELF like[c] %#", argumentArray: [key])
results = prices.filter{predicate.evaluate(with: ($0.MultipleSearchString?.lowercased())!)}
length += 1} while(length < text.count)
//Third edition:
let results = prices.filter{ text.reduce((($0.MultipleSearchString?.lowercased())!, true)){
if !$0.1 {return ("",false)}
else if let index = $0.0.index(of: $1) {return (String($0.0[index...]),true)}
return ("",false)
}.1}
self.items.append(contentsOf: results)
self.tableView.reloadData()
}
}

Related

Trim String before specific character?

I have a string (asderwt.qwertyu.zxcvbbnnhg) and I want to change it to .qwertyu.zxcvbbnnhg.
I have tried to do this using the following code:
Var str = "asderwt.qwertyu.zxcvbbnnhg"
if let dotRange = str.range(of: ".") {
str.removeSubrange(dotRange.lowerBound..<str.startIndex)
print("AAAAAAAAAA: \(str)")
}
Get the first index of the dot and get the substring after that index
let str = "asderwt.qwertyu.zxcvbbnnhg"
if let index = str.firstIndex(where: { $0 == "." }) {
print(str[index...])//.qwertyu.zxcvbbnnhg
}
You are on the right track, but here
str.removeSubrange(dotRange.lowerBound..<str.startIndex)
the range bounds are in the wrong order. It should be
var str = "asderwt.qwertyu.zxcvbbnnhg"
if let dotRange = str.range(of: ".") {
str.removeSubrange(str.startIndex..<dotRange.lowerBound) // <-- HERE
print("Result: \(str)") // Result: .qwertyu.zxcvbbnnhg
}
You can also use a “partial range”
if let dotRange = str.range(of: ".") {
str.removeSubrange(..<dotRange.lowerBound)
print("Result: \(str)") // Result: .qwertyu.zxcvbbnnhg
}
Or with firstIndex(of:) instead of range(of:):
if let dotIndex = str.firstIndex(of: ".") {
str.removeSubrange(..<dotIndex)
print("Result: \(str)") // Result: .qwertyu.zxcvbbnnhg
}

Loop is taking too long to process

I am using following to code to arrange the array of contacts in sections (for e.g. contact with prefix "A" should show under "A" section). If there are contacts 4-5 hundred then it takes 20 sec to process.
Can you please check what is issue here? or any alternate for this.
let finalArr = NSMutableArray()
for contactDetail in conatctsArr {
let name = (contactDetail as! Dictionary)["givenName"] ?? ""// Getting First character of name
var key = String()
if name.characters.count > 0 {
let index1 = name.index((name.startIndex), offsetBy: 1)
key = name.substring(to: index1)
}
else {
key = ""
}
// getting all contatcts starting with particular character
let predicate=NSPredicate(format: "SELF.givenName beginswith[c] %#",key)
let filteredArr = (conatctsArr as NSArray).filtered(using: predicate)
var dic = Dictionary<String, Any>()
dic["key"] = key
dic["values"] = filteredArr
if filteredArr.count > 0 && !(finalArr.contains(dic)) {
finalArr.add(dic)
}
}
Removing the filtered elements from the array after processing in each loop might improve the performance. Try:
let finalArr = NSMutableArray()
var processingArray = NSMutableArray(array:conatctsArr)
while processingArray.count > 0 {
let contactDetail = processingArray[0]
let name = (contactDetail as! Dictionary)["givenName"] ?? ""
var key = String()
if name.characters.count > 0 {
let index1 = name.index((name.startIndex), offsetBy: 1)
key = name.substring(to: index1)
}
else {
key = ""
}
let predicate=NSPredicate(format: "SELF.givenName beginswith[c] %#",key)
let filteredArr = processingArray.filtered(using: predicate)
if filteredArr.count > 0 {
var dic = Dictionary<String, Any>()
dic["key"] = key
dic["values"] = filteredArr
finalArr.add(dic)
}
processingArray.removeObjects(in: filteredArr)
}
In your code, the filtering is being done multiple times for the same key. Try :
let finalArr = NSMutableArray()
for contactDetail in conatctsArr
{
let keysArray = [“A”, “B”, “C”,…. “Z”]
for key in keysArray
{
let predicate=NSPredicate(format: "SELF.givenName beginswith[c] %#",key)
let filteredArr = (conatctsArr as NSArray).filtered(using: predicate)
var dic = Dictionary<String, Any>()
dic["key"] = key
dic["values"] = filteredArr
if filteredArr.count > 0) {
finalArr.add(dic)
}
}
}

Email Validation is wrong as per Regex

I am using email validation into my project which method is like below
//MARK: isValidEmailID
func isValidEmail(testStr:String) -> Bool {
print("validate emilId: \(testStr)")
let emailRegEx = "^(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?(?:(?:(?:[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+(?:\\.[-A-Za-z0-9!#$%&’*+/=?^_'{|}~]+)*)|(?:\"(?:(?:(?:(?: )*(?:(?:[!#-Z^-~]|\\[|\\])|(?:\\\\(?:\\t|[ -~]))))+(?: )*)|(?: )+)\"))(?:#)(?:(?:(?:[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)(?:\\.[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)*)|(?:\\[(?:(?:(?:(?:(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))\\.){3}(?:[0-9]|(?:[1-9][0-9])|(?:1[0-9][0-9])|(?:2[0-4][0-9])|(?:25[0-5]))))|(?:(?:(?: )*[!-Z^-~])*(?: )*)|(?:[Vv][0-9A-Fa-f]+\\.[-A-Za-z0-9._~!$&'()*+,;=:]+))\\])))(?:(?:(?:(?: )*(?:(?:(?:\\t| )*\\r\\n)?(?:\\t| )+))+(?: )*)|(?: )+)?$"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
let result = emailTest.evaluateWithObject(testStr)
return result
}
OR
func isValidEmailID(email: String) -> Bool {
let regExPattern: String = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailValidator: NSPredicate = NSPredicate(format: "SELF MATCHES %#", regExPattern)
let isValid: Bool = emailValidator.evaluateWithObject(email)
return isValid
}
This both Regex works fine when I enter "modijecky#gmail.com" or any other wrong input but it will not work when I enter "modijecky#gmail.com.com".
So,I find out that "name#.com.com" is a valid email address and there are more sub-domains like this. So now I want user not to enter sub-domains. Is there any REGEX that validate email address within just one domain like "name#gmail.com" not with multiple domains or sub-domains.
I also try different Regex from google and implement it into project but same problem occurs.
Please help me with it.
Thank you
Don’t reinvent the wheel:
Not Reinventing the Wheel: Email Validation in Swift
Basically you can use NSDataDetector to do the heavy lifting and have everything consistent and updated to the way it works in macOS and iOS natively. Not only that but you also avoid regex headaches.
// Simplifying the example from the website a bit
import Foundation
func validate(_ text: String) -> Bool {
let types = NSTextCheckingResult.CheckingType.link.rawValue
guard
let dataDetector = try? NSDataDetector(types: types),
let match = dataDetector
.matches(in: text, options: [], range: NSRangeFromString(text))
.first,
let absoluteString = match.url?.absoluteString
else { return false }
return absoluteString == "mailto:\(text)"
}
validate("test#gmail.com") // -> true
validate(" test#gmail.com") // -> false
This will make sure that the entire text is a single, valid email address without any superfluous characters.
Function Call:
let result = isValidEmail(testStr: "test#test.com.op")
if (result)
{
print ("passed")
}
else{
print ("failed")
}
Function Definition:
func isValidEmail(testStr:String) -> Bool {
// print("validate calendar: \(testStr)")
var returnValue : Bool = false
let emailRegEx = "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
let emailTest = NSPredicate(format:"SELF MATCHES %#", emailRegEx)
if (emailTest.evaluate(with: testStr))
{
let fullNameArr = testStr.components(separatedBy: "#")
let IdName = fullNameArr[0]
let domainName = fullNameArr[1]
var number = 0
let string = domainName
for character in domainName.characters {
if character == "."
{
number = number + 1
}
}
if number <= 1
{
returnValue = true
}
}
return returnValue
}
Result:
you should have this code to don't allow subdomain.
func isValidEmail(email:String) -> Bool {
if email.range(of: "#") == nil || email.range(of: ".") == nil{
return false
}
let accountName = email.substring(to: email.range(of: "#")!.lowerBound)
let domainName = email.substring(from: email.range(of: "#")!.upperBound)
let subDomain = domainName.substring(from: email.range(of: ".")!.lowerBound)
//filter for user name
let unWantedInUName = " ~!##$^&*()={}[]|;’:\"<>,?/`";
//filter for domain
let unWantedInDomain = " ~!##$%^&*()={}[]|;’:\"<>,+?/`";
//filter for subdomain
let unWantedInSub = " `~!##$%^&*()={}[]:\";’<>,?/1234567890";
//subdomain should not be less that 2 and not greater 6
if(!(subDomain.characters.count>=2 && subDomain.characters.count<=6)) {
return false;
}
if (accountName == "" || accountName.rangeOfCharacter(from: CharacterSet.init(charactersIn: unWantedInUName)) != nil || domainName == "" || domainName.rangeOfCharacter(from: CharacterSet.init(charactersIn: unWantedInDomain)) != nil || subDomain == "" || subDomain.rangeOfCharacter(from: CharacterSet.init(charactersIn: unWantedInSub)) != nil ) {
return false
}
return true
}

Ignore a letter in swift which starts with a Lower Case

Here's what I am trying to do :
let courseName = "Bachelor of Tourism Administration(B.T.A)".condensedWhitespace
let upperCaseCourseName = courseName.uppercaseString
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") { $0.0 + String($0.1.characters.first!) }
let upperCasecourseFirstCharcters = extrctCourseName
print(upperCasecourseFirstCharcters) // output : "BOTA" but i want "BTA"
as you see that my outPut of "Bachelor of Tourism Administration(B.T.A)" is BOTA but the desired output is BTA because word of is starting from a lowerCase and i want to ignore that word in my this method , how am gonna do that any idea ?
let courseName = "Bachelor of Tourism Administration(B.T.A)" //.condensedWhitespace
var newString = ""
let array : NSArray = courseName.componentsSeparatedByString(" ")
for chr in array {
let str = chr as! NSString
if str.lowercaseString != str{
if newString.characters.count > 0{
newString = newString.stringByAppendingString(" "+(str as String))
continue
}
newString = newString.stringByAppendingString((str as String))
}
}
let upperCaseCourseName = newString.uppercaseString
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") { $0.0 + String($0.1.characters.first!) }
let upperCasecourseFirstCharcters = extrctCourseName
print(upperCasecourseFirstCharcters)
//This will defiantly meet to your problem/. Let me know if it works for u or not
You can paste this into a playground:
extension String {
func array() -> [String] {
return self.componentsSeparatedByString(" ")
}
func abbreviate() -> String {
var output = ""
let array = self.array()
for word in array {
let index = word.startIndex.advancedBy(0)
let str = String(word[index])
if str.lowercaseString != str {
output += str
}
}
return output
}
}
let courseName = "Bachelor of Tourism Administration(B.T.A)".abbreviate()
print(courseName) // prints BTA
A clean approach would be:
extension Character
{
public func isUpper() -> Bool
{
let characterString = String(self)
return (characterString == characterString.uppercaseString) && (characterString != characterString.lowercaseString)
}
}
let courseName = "Bachelor of Tourism Administration(B.T.A)"
let upperCaseCourseName = courseName
let extrctCourseName = upperCaseCourseName.componentsSeparatedByString(" ").reduce("") {
if($0.1.characters.first!.isUpper()) {
return $0.0 + String($0.1.characters.first!)
}else {
return $0.0
}
}

NSPredicate filter array using all characters before delimiter

I am currently filtering using
self.searchArray.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (Array(exampleArray) as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.searchArray = array as! [String]
It is filtering the whole string, However, I only want to filter using all characters that exist before my delimiter. For example:
each array value has a delimiter which is "$%^"
example array contains [abc$%^12], [efg$%^32], [tyh$%^77]
I only want the filtering to include on all characters before $%^ which would be abc, efg, and tyh
You can do this without using NSPredicate. Stub:
let searchBarText = "h"
let exampleArray = ["abc$%^12", "efg$%^32", "tyh$%^77"]
let searchResults = exampleArray.filter {
let components = $0.componentsSeparatedByString("$%^")
return components[0].containsString(searchBarText)
}
'pure' Swift solution :-) (I don't like any help of Foundation)
let arr = ["abc$%^12", "efg$%^32", "tyh$%^77", "nodelemiter"]
let delimiter = "$%^"
func splitByDelimeter(let str: String, delimiter: String)->(String,String) {
var str1 = ""
var str2 = str
while !str2.hasPrefix(delimiter) && str2.characters.count > 0 {
str1.append(str2.removeAtIndex(str2.startIndex))
}
if (str1 == str) { return (str1, "") }
let r = Range(start: str2.startIndex, end: str2.startIndex.advancedBy(delimiter.characters.count))
str2.removeRange(r)
return (str1, str2)
}
let res = arr.map { splitByDelimeter($0, delimiter: delimiter).0 }
print(res) // ["abc", "efg", "tyh", "nodelemiter"]
let res2 = arr.map { splitByDelimeter($0, delimiter: delimiter) }
print(res2) // [("abc", "12"), ("efg", "32"), ("tyh", "77"), ("nodelemiter", "")]
it should work, even there is only delimiter, or more than one delimiter in your string. First occurrence of delimiter will split the string to two parts.
UPDATE
with help of String extension you can do it as ...
let arr = ["abc$%^12", "efg$%^32", "tyh$%^77", "nodelemiter", "$%^jhdk$%^jdhjahsd", "22lemar$%^fralemdo"]
extension String {
func splitBy(delimiter: String)->(String,String) {
var str1 = ""
var str2 = self
while !str2.hasPrefix(delimiter) && str2.characters.count > 0 {
str1.append(str2.removeAtIndex(str2.startIndex))
}
if (str1 == self) { return (str1, "") }
let r = Range(start: str2.startIndex, end: str2.startIndex.advancedBy(delimiter.characters.count))
str2.removeRange(r)
return (str1, str2)
}
func contains(string: String)->Bool {
guard !self.isEmpty else {
return false
}
var s = self.characters.map{ $0 }
let c = string.characters.map{ $0 }
repeat {
if s.startsWith(c){
return true
} else {
s.removeFirst()
}
} while s.count > c.count - 1
return false
}
}
let filtered = arr.map{ $0.splitBy("$%^").0 }.filter { $0.contains("lem") }
print(filtered) // ["nodelemiter", "22lemar"]
or you can use hasPrefix to narrow search by typing more chars to you search box ... etc.
let arr2 = ["abc$%^12", "abefg$%^32", "tyhab$%^77"]
let filtered2 = arr2.map{ $0.splitBy("$%^").0 }.filter { $0.hasPrefix("ab") }
print(filtered2) // ["abc", "abefg"]

Resources