I'm working on search and want to get it error-proof.
let's say we have 3 strings: containsText that contains 2 words I'm looking for in fullTextShort and fullTextLong
My func contains works with fullTextShort as the words that I'm looking for are right after each other, but it doesn't work with fullTextLong where there's a world in between.
How to get the func to return true for both cases?
struct ContainsFuncView: View {
let fullTextShort = "I like pineapple"
let fullTextLong = "I like green pineapple"
let containsText = "like pineapple"
var body: some View {
VStack {
contains(type: "Short")
contains(type: "Long")
}
}
func contains(type: String) -> Text {
var containsInFullText: Bool = false
if type == "Short" {
containsInFullText = fullTextShort.localizedStandardContains(containsText)
}
else if type == "Long" {
containsInFullText = fullTextLong.localizedStandardContains(containsText)
}
return Text("\(containsInFullText ? "Contains" : "Doesn't contain") in fullText\(type)").foregroundColor(containsInFullText ? .green : .red)
}
}
Thank you!
Split containsText at the space. Then test if any or all words are in the text:
containsInFullText = containsText.components(separatedBy: " ").contains { fullTextShort.localizedStandardContains($0) }
containsInFullText = containsText.components(separatedBy: " ").allSatisfy { fullTextShort.localizedStandardContains($0) }
You can split the searched text into words and the reduce the search results for each word so the search returns true only when all words are found
let words = containsText.split(separator: " ")
if type == "Short" {
containsInFullText = words.reduce(true) { contains, word in
contains && fullTextShort.localizedStandardContains(word)
}
}
else if type == "Long" {
containsInFullText = words.reduce(true) { contains, word in
contains && fullTextLong.localizedStandardContains(word)
}
}
If you want to return true when at least one word is found, swap the && to || and the initial value to false
if type == "Short" {
containsInFullText = words.reduce(false) { contains, word in
contains || fullTextShort.localizedStandardContains(word)
}
}
else if type == "Long" {
containsInFullText = words.reduce(false) { contains, word in
contains || fullTextLong.localizedStandardContains(word)
}
}
Related
lets say we have an extension of String:
extension String {
var textBeCleared: Bool {
// how we write here
}
}
then:
var demo1 = "" // no space over here
demo.textBeCleared // true
var demo2 = " " // type one space over here
demo.textBeCleared // false
because I got a sticky issue is: I have a save button at bottom of a text field, save button only displays when removing all text contents in the text field, like demo1. but if type more space over here, like demo2, save button will be hidden.
User filter for this purpose. It will filter each char in the string and you will get your expected result.
let inputString1 = " "
let inputString2 = ""
let inputString3 = "a"
let spacesCount1 = inputString1.filter { $0 == " " }
print(spacesCount1.count)
let spacesCount2 = inputString2.filter { $0 == " " }
print(spacesCount2.count)
let spacesCount3 = inputString3.filter { $0 == " " }
print(spacesCount3.count)
Output
3
0
0
thanks guys, I managed solved this by following code
let inputString1 = " "
let inputString2 = ""
let inputString3 = "a"
extension String {
var textBeCleared: Bool {
!self.contains(" ") && allSatisfy { $0.isWhitespace }
}
}
inputString1.textBeCleared // false
inputString2.textBeCleared // true
inputString3.textBeCleared // false
you could try this:
extension String {
var textBeCleared: Bool {
self.isEmpty
}
}
var demo1 = "" // no space over here
var demo2 = " " // type one space over here
print(" demo1.textBeCleared: \(demo1.textBeCleared) ") // true
print(" demo2.textBeCleared: \(demo2.textBeCleared) ") // false
I have an array of managed object class named as categories
In every category, I have NSSet of apCodes
I am showing the category name in the table view section and opCodes name in table view rows.enter image description here
I want to search operation by apcodes, not by category name, but I want to show only those categories whose apcode name matches with the search text.
Here is my code, please correct my function, I unable to filter apcodes properly.
Note: I have copied the original categories array in originalCategories
func filterArray(text : String) {
categories = originalCategories
categories = categories?.compactMap { category in
guard !text.isEmpty else {
return category
}
var apCodes = [ApCode]()
if let apc = category.apCodes?.allObjects as? [ApCode] {
apCodes = apc.filter { (apcode) -> Bool in
if apcode.apCodeName?.lowercased().contains(text.lowercased()) ?? false {
return true
} else {
return false
}
}
}
if apCodes.count == 0 {
return nil
} else {
category.apCodes = NSSet(array: apCodes)
return category
}
}
if text == Constant.EMPTY_STRING {
categories = originalCategories
}
}
Wildcard Pattern Matching: Given a string and a pattern containing wildcard characters i.e. * and ?, where ? can match to any single character in the input string and * can match to any number of characters including zero characters, design an efficient algorithm to find if the pattern matches with the complete input string or not.
For example:
Input: string = "xyxzzxy", pattern = "x***y"
Output: Match
Input: string = "xyxzzxy", pattern = "x***x"
Output: No Match
Input: String = "xyxzzxy", pattern = "x***x?"
Output: Match
Input: String = "xyxzzxy", pattern = "*"
Output: Match
With the help of Foundation classes (in particular NSPredicate) you can implement wildcard matching simply as
func wildcard(_ string: String, pattern: String) -> Bool {
let pred = NSPredicate(format: "self LIKE %#", pattern)
return !NSArray(object: string).filtered(using: pred).isEmpty
}
The LIKE comparison does exactly what you want:
The left hand expression equals the right-hand expression: ? and * are allowed as wildcard characters, where ? matches 1 character and * matches 0 or more characters.
Examples:
print(wildcard("xyxzzxy", pattern: "x***y")) // true
print(wildcard("xyxzzxy", pattern: "x***x")) // false
print(wildcard("xyxzzxy", pattern: "x***x?")) // true
print(wildcard("xyxzzxy", pattern: "*")) // true
print(wildcard("a12b34c", pattern: "a?b?c")) // false
print(wildcard("a12b34c", pattern: "a*b*c")) // true
If the question is to "design an efficient algorithm...", you could define an extension on String this way:
extension String {
func matches(wildcard pattern: String) -> Bool {
var strIndex = self.startIndex, matchIndex = self.startIndex
var patternIndex = pattern.startIndex, asteriskIndex = pattern.endIndex
while strIndex < self.endIndex {
//Characters match, or question mark
if patternIndex < pattern.endIndex
&& (self[strIndex] == pattern[patternIndex] || pattern[patternIndex] == "?") {
strIndex = self.index(after: strIndex)
patternIndex = pattern.index(after: patternIndex)
}
//Asterisk character
else if patternIndex < pattern.endIndex && pattern[patternIndex] == "*" {
asteriskIndex = patternIndex
matchIndex = strIndex
patternIndex = pattern.index(after: patternIndex)
}
else if asteriskIndex != pattern.endIndex {
patternIndex = pattern.index(after: asteriskIndex)
matchIndex = self.index(after: matchIndex)
strIndex = matchIndex
}
else { return false }
}
//Asterisk character at the end of the pattern
while patternIndex < pattern.endIndex && pattern[patternIndex] == "*" {
patternIndex = pattern.index(after: patternIndex)
}
return patternIndex == pattern.endIndex
}
}
It is a more readable version of this code.
Here are some test cases:
"xyxzzxy".matches(wildcard: "x***y") //true
"xyxzzxy".matches(wildcard: "x***x") //false
"xyxzzxy".matches(wildcard: "x***x?") //true
"xyxzzxy".matches(wildcard: "*") //true
Taking Martin's solution a step further, here's a [String] extension that will accept a pattern and return all matching elements:
extension Array where Element == String {
func wildcard(pattern: String) -> [String] {
var returnArray: [String] = []
for item in self {
if (wildcard(item, pattern: pattern)) {
returnArray.append(item)
}
}
return returnArray
}
// Credit to Martin R # SO for this brilliance: https://stackoverflow.com/a/57271935/215950
private func wildcard(_ string: String, pattern: String) -> Bool {
let pred = NSPredicate(format: "self LIKE %#", pattern)
return !NSArray(object: string).filtered(using: pred).isEmpty
}
}
func matchingString() {
var savingValueOfJ = 0
var boolean = [Bool]()
inputString = inputStringTextField.text!
pattern = patternTextField.text!
let inputCharacters = Array(inputString)
let patternCharacters = Array(pattern)
for (index, firstCharacter) in patternCharacters.enumerated() {
if index == patternCharacters.count - 1, index != 0 {
if inputCharacters.last == firstCharacter || firstCharacter == "*" || firstCharacter == "?" {
boolean.append(true)
break
}
else {
boolean.append(false)
break
}
} else {
if firstCharacter != "*" {
while savingValueOfJ <= inputCharacters.count {
if firstCharacter == inputCharacters[savingValueOfJ] || firstCharacter == "?" {
boolean.append(true)
savingValueOfJ += 1
break
} else {
boolean.append(false)
savingValueOfJ += 1
break
}
}
}
}
}
let arr = boolean.filter{ $0 == false}
if arr.count > 0 {
displayingResultLbl.text = "Not A Match"
}
else {
displayingResultLbl.text = "Matche's"
}
}
ruby has the function string.squeeze, but I can't seem to find a swift equivalent.
For example I want to turn bookkeeper -> bokepr
Is my only option to create a set of the characters and then pull the characters from the set back to a string?
Is there a better way to do this?
Edit/update: Swift 4.2 or later
You can use a set to filter your duplicated characters:
let str = "bookkeeper"
var set = Set<Character>()
let squeezed = str.filter{ set.insert($0).inserted }
print(squeezed) // "bokepr"
Or as an extension on RangeReplaceableCollection which will also extend String and Substrings as well:
extension RangeReplaceableCollection where Element: Hashable {
var squeezed: Self {
var set = Set<Element>()
return filter{ set.insert($0).inserted }
}
}
let str = "bookkeeper"
print(str.squeezed) // "bokepr"
print(str[...].squeezed) // "bokepr"
I would use this piece of code from another answer of mine, which removes all duplicates of a sequence (keeping only the first occurrence of each), while maintaining order.
extension Sequence where Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
var alreadyAdded = Set<Iterator.Element>()
return self.filter { alreadyAdded.insert($0).inserted }
}
}
I would then wrap it with some logic which turns a String into a sequence (by getting its characters), unqiue's it, and then restores that result back into a string:
extension String {
func uniqueCharacters() -> String {
return String(self.characters.unique())
}
}
print("bookkeeper".uniqueCharacters()) // => "bokepr"
Here is a solution I found online, however I don't think it is optimal.
func removeDuplicateLetters(_ s: String) -> String {
if s.characters.count == 0 {
return ""
}
let aNum = Int("a".unicodeScalars.filter{$0.isASCII}.map{$0.value}.first!)
let characters = Array(s.lowercased().characters)
var counts = [Int](repeatElement(0, count: 26))
var visited = [Bool](repeatElement(false, count: 26))
var stack = [Character]()
var i = 0
for character in characters {
if let num = asciiValueOfCharacter(character) {
counts[num - aNum] += 1
}
}
for character in characters {
if let num = asciiValueOfCharacter(character) {
i = num - aNum
counts[i] -= 1
if visited[i] {
continue
}
while !stack.isEmpty, let peekNum = asciiValueOfCharacter(stack.last!), num < peekNum && counts[peekNum - aNum] != 0 {
visited[peekNum - aNum] = false
stack.removeLast()
}
stack.append(character)
visited[i] = true
}
}
return String(stack)
}
func asciiValueOfCharacter(_ character: Character) -> Int? {
let value = String(character).unicodeScalars.filter{$0.isASCII}.first?.value ?? 0
return Int(value)
}
Here is one way to do this using reduce(),
let newChar = str.characters.reduce("") { partial, char in
guard let _ = partial.range(of: String(char)) else {
return partial.appending(String(char))
}
return partial
}
As suggested by Leo, here is a bit shorter version of the same approach,
let newChar = str.characters.reduce("") { $0.range(of: String($1)) == nil ? $0.appending(String($1)) : $0 }
Just Another solution
let str = "Bookeeper"
let newChar = str.reduce("" , {
if $0.contains($1) {
return "\($0)"
} else {
return "\($0)\($1)"
}
})
print(str.replacingOccurrences(of: " ", with: ""))
Use filter and contains to remove duplicate values
let str = "bookkeeper"
let result = str.filter{!result.contains($0)}
print(result) //bokepr
I like many of the features in Swift, but using manipulating strings are still a big pain in the ass.
func checkPalindrome(word: String) -> Bool {
print(word)
if word == "" {
return true
} else {
if word.characters.first == word.characters.last {
return checkPalindrome(word.substringWithRange(word.startIndex.successor() ..< word.endIndex.predecessor()))
} else {
return false
}
}
}
This code fails miserably whenever the string's length is an odd number. Of course I could make it so the first line of the block would be if word.characters.count < 2, but is there a way in Swift to get substrings and check easily?
Update
I like many of the suggestions, but I guess the original question could be misleading a little, since it's a question about String more than getting the right results for the function.
For instance, in Python, checkPalindrome(word[1:-1]) would work fine for the recursive definition, whereas Swift code is much less graceful since it needs other bells and whistles.
return word == String(word.reversed())
func isPalindrome(myString:String) -> Bool {
let reverseString = String(myString.characters.reversed())
if(myString != "" && myString == reverseString) {
return true
} else {
return false
}
}
print(isPalindrome("madam"))
I have used the below extension to find whether the number is Palindrome or Not.
extension String {
var isPalindrome: Bool {
return self == String(self.reversed())
}
}
Sometimes having a front end for a recursion can simplify life. I sometimes do this when the arguments which are most convenient to use are not what I want in the user interface.
Would the following meet your needs?
func checkPalindrome(str: String) -> Bool {
func recursiveTest(var charSet: String.CharacterView) -> Bool {
if charSet.count < 2 {
return true
} else {
if charSet.popFirst() != charSet.popLast() {
return false
} else {
return recursiveTest(charSet)
}
}
}
return recursiveTest(str.characters)
}
just add on more condition in if
func checkPalindrome(word: String) -> Bool {
print(word)
if (word == "" || word.characters.count == 1){
return true
}
else {
if word.characters.first == word.characters.last {
return checkPalindrome(word.substringWithRange(word.startIndex.successor() ..< word.endIndex.predecessor()))
} else {
return false
}
}
}
extension StringProtocol where Self: RangeReplaceableCollection {
var letters: Self { filter(\.isLetter) }
var isPalindrome: Bool {
let letters = self.letters
return String(letters.reversed()).caseInsensitiveCompare(letters) == .orderedSame
}
}
"Dammit I'm Mad".isPalindrome // true
"Socorram-me subi no onibus em marrocos".isPalindrome // true
You can also break your string into an array of characters and iterate through them until its half comparing one by one with its counterpart:
func checkPalindrome(_ word: String) -> Bool {
let chars = Array(word.letters.lowercased())
for index in 0..<chars.count/2 {
if chars[index] != chars[chars.count - 1 - index] {
return false
}
}
return true
}
And the recursive version fixing the range issue where can't form a range with endIndex < startIndex:
func checkPalindrome<T: StringProtocol>(_ word: T) -> Bool {
let word = word.lowercased()
.components(separatedBy: .punctuationCharacters).joined()
.components(separatedBy: .whitespacesAndNewlines).joined()
if word == "" || word.count == 1 {
return true
} else {
if word.first == word.last {
let start = word.index(word.startIndex,offsetBy: 1, limitedBy: word.endIndex) ?? word.startIndex
let end = word.index(word.endIndex,offsetBy: -1, limitedBy: word.startIndex) ?? word.endIndex
return checkPalindrome(word[start..<end])
} else {
return false
}
}
}
checkPalindrome("Dammit I'm Mad")
I think if you make an extension to String like this one then it will make your life easier:
extension String {
var length: Int { return characters.count }
subscript(index: Int) -> Character {
return self[startIndex.advancedBy(index)]
}
subscript(range: Range<Int>) -> String {
return self[Range<Index>(start: startIndex.advancedBy(range.startIndex), end: startIndex.advancedBy(range.endIndex))]
}
}
With it in place, you can change your function to this:
func checkPalindrome(word: String) -> Bool {
if word.length < 2 {
return true
}
if word.characters.first != word.characters.last {
return false
}
return checkPalindrome(word[1..<word.length - 1])
}
Quick test:
print(checkPalindrome("aba")) // Prints "true"
print(checkPalindrome("abc")) // Prints "false"
extension String {
func trimmingFirstAndLastCharacters() -> String {
guard let startIndex = index(self.startIndex, offsetBy: 1, limitedBy: self.endIndex) else {
return self
}
guard let endIndex = index(self.endIndex, offsetBy: -1, limitedBy: self.startIndex) else {
return self
}
guard endIndex >= startIndex else {
return self
}
return String(self[startIndex..<endIndex])
}
var isPalindrome: Bool {
guard count > 1 else {
return true
}
return first == last && trimmingFirstAndLastCharacters().isPalindrome
}
}
We first declare a function that removes first and last characters from a string.
Next we declare a computer property which will contain the actual recursive code that checks if a string is palindrome.
If string's size is less than or equal 1 we immediately return true (strings composed by one character like "a" or the empty string "" are considered palindrome), otherwise we check if first and last characters of the string are the same and we recursively call isPalindrome on the current string deprived of the first and last characters.
Convert the string into an Array. When the loop is executed get the first index and compare with the last index.
func palindrome(string: String)-> Bool{
let char = Array(string)
for i in 0..<char.count / 2 {
if char[i] != char[char.count - 1 - i] {
return false
}
}
return true
}
This solution is not recursive, but it is a O(n) pure index based solution without filtering anything and without creating new objects. Non-letter characters are ignored as well.
It uses two indexes and walks outside in from both sides.
I admit that the extension type and property name is stolen from Leo, I apologize. 😉
extension StringProtocol where Self: RangeReplaceableCollection {
var isPalindrome : Bool {
if isEmpty { return false }
if index(after: startIndex) == endIndex { return true }
var forward = startIndex
var backward = endIndex
while forward < backward {
repeat { formIndex(before: &backward) } while !self[backward].isLetter
if self[forward].lowercased() != self[backward].lowercased() { return false }
repeat { formIndex(after: &forward) } while !self[forward].isLetter
}
return true
}
}
Wasn't really thinking of this, but I think I came up with a pretty cool extension, and thought I'd share.
extension String {
var subString: (Int?) -> (Int?) -> String {
return { (start) in
{ (end) in
let startIndex = start ?? 0 < 0 ? self.endIndex.advancedBy(start!) : self.startIndex.advancedBy(start ?? 0)
let endIndex = end ?? self.characters.count < 0 ? self.endIndex.advancedBy(end!) : self.startIndex.advancedBy(end ?? self.characters.count)
return startIndex > endIndex ? "" : self.substringWithRange(startIndex ..< endIndex)
}
}
}
}
let test = ["Eye", "Pop", "Noon", "Level", "Radar", "Kayak", "Rotator", "Redivider", "Detartrated", "Tattarrattat", "Aibohphobia", "Eve", "Bob", "Otto", "Anna", "Hannah", "Evil olive", "Mirror rim", "Stack cats", "Doom mood", "Rise to vote sir", "Step on no pets", "Never odd or even", "A nut for a jar of tuna", "No lemon, no melon", "Some men interpret nine memos", "Gateman sees name, garageman sees nametag"]
func checkPalindrome(word: String) -> Bool {
if word.isEmpty { return true }
else {
if word.subString(nil)(1) == word.subString(-1)(nil) {
return checkPalindrome(word.subString(1)(-1))
} else {
return false
}
}
}
for item in test.map({ $0.lowercaseString.stringByReplacingOccurrencesOfString(",", withString: "").stringByReplacingOccurrencesOfString(" ", withString: "") }) {
if !checkPalindrome(item) {
print(item)
}
}
A simple solution in Swift:
func isPalindrome(word: String) -> Bool {
// If no string found, return false
if word.count == 0 { return false }
var index = 0
var characters = Array(word) // make array of characters
while index < characters.count / 2 { // repeat loop only for half length of given string
if characters[index] != characters[(characters.count - 1) - index] {
return false
}
index += 1
}
return true
}
func checkPalindrome(_ inputString: String) -> Bool {
if inputString.count % 2 == 0 {
return false
} else if inputString.count == 1 {
return true
} else {
var stringCount = inputString.count
while stringCount != 1 {
if inputString.first == inputString.last {
stringCount -= 2
} else {
continue
}
}
if stringCount == 1 {
return true
} else {
return false
}
}
}