How to make sure that a string contains a whole word not just part of it - ios

I need some code to make sure that if a whole word exists in a return formatted text file it is accepted and that, if only part of it is present, it is not considered.
If I type lau in the TextField it is accepted and I would rather the answer was false until a whole word is matched
Here is the file limited.txt I use in my project. Each word is on a separate line:
appetitive
appetitiveness
appetitost
appetize
appetized
appetizement
appetizer
appetizers
appetizing
appetizingly
appinite
appius
appl
applanate
applanation
applaud
applaudable
applaudably
applauded
applauder
applauders
applauding
applaudingly
applauds
applause
applauses
applausive
applausively
apple
appleberry
appleblossom
applecart
appled
appledrane
appledrone
applegrower
applejack
applejohn
applemonger
Thanks for your help
import SwiftUI
struct ContentView: View{
#ObservedObject var textFileStringContent: TexFileReader
#State private var text = ""
var body: some View{
VStack {
TextField("please type the word to check", text: $text)
// so that it does not matter if user capitalises a word
if textFileStringContent.data.contains(self.text.lowercased()) {
Text("part of it exists")
// I tried to code it in here but to no avail
// if it is a whole word {
// Text("congratulations it does exist")
// }
} else if !text.isEmpty {
Text("sorry no such word")
}
}.padding().font(.headline)
.navigationBarTitle("Word checker")
}
}
class TexFileReader: ObservableObject {
#Published var data: String = ""
init() { self.load(file: "limited") }
func load(file: String) {
if let filepath = Bundle.main.path(forResource: file, ofType: "txt") {
do {
let contents = try String(contentsOfFile: filepath)
DispatchQueue.main.async {
self.data = contents
print(self.data.contains("lau"))
// this prints true even if lau is not a whole word
// applaud
// applaudable
// applaudably
// applauded
// applauder
// applauders
// applauding
// applaudingly
// applauds
// applause
// applauses
// applausive
// applausively
// but present in each of these
// I need to make sure that the match is a whole word not just part of one
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("File not found")
}
}
}

A possible way is to search with Regular Expression and the word boundary specifier \\b
if textFileStringContent.data.range(of: "\\b\(self.text)\\b", options: [.caseInsensitive, .regularExpression]) != nil {

You may check if it ends with a newline separator in your text file:
let textWithNewline = self.text.lowercased() + "\n"
if textFileStringContent.data.contains(textWithNewline) {
// it is a whole word
}

Foundation contains a language analysis engine NSLinguisticTagger which can do many things including finding specific words with locale sensitivity.
A simple implementation of what you're trying to do is:
//extension via https://stackoverflow.com/questions/15062458/shortcut-to-generate-an-nsrange-for-entire-length-of-nsstring/56391610#56391610
extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
}
var tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagScheme.tokenType], options: 0)
let baddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders catlau
applauding
"""
let gooddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders lau catlau
applauding
"""
var foundLau = false
tagger.string = baddata
tagger.enumerateTags(in: NSRange(location: 0, length: baddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = baddata.range(from: tokenRange) {
let fragment = baddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in baddata =", foundLau ? "true":"false")
tagger.string = gooddata
tagger.enumerateTags(in: NSRange(location: 0, length: gooddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = gooddata.range(from: tokenRange) {
let fragment = gooddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in gooddata =", foundLau ? "true":"false")
enumerateTags returns an NSRange which can be converted to Range for general Swift use.

Related

SwiftUI View not updating when #ObservedObject changes using GCD

I'm still in the early stages with SwiftUI. Everything's been fine up until now, but I'm running into problems with using GCD - never worked with multi-threading before.
I am getting data from various sensors over TCP continuously running in the background. All this seems fine, I can see the data coming across and it appears to be getting stored where I want it...
Raw data
$AIMTW,26.1,C*1E
Contents of struct
(src: "AI", timeStamp: 2021-09-11 10:43:01 +0000, temp: 26.1, units: "ΒΊC")
I think I'm getting the data processing happening on the main thread where it should update my View, but it doesn't update. I expect I've missed something pretty fundamental, but I just can't work out what it is. Any help would be appreciated.
This should hopefully be enough to reproduce the problem. Originally the code would be reading in a constant stream of data from a TCP source; I have modified it to read from a text file. The View should update after each line of the text file.
import Foundation
struct NMEA{
// Mean Temperature of Water
var mtw = (src: "", // Source
timeStamp: Date(), // Generated Time Stamp
temp: 0.0, // temperature
units: "") // unit of measurement, Celsius (C)
}
import SwiftUI
struct ContentView: View {
#ObservedObject var nmeaHandler = NMEAhandler()
var body: some View {
HStack{
Text("Water Temperature: ")
Text(String(nmeaHandler.nmea.mtw.temp))
Text(nmeaHandler.nmea.mtw.units)
}
.onAppear{
nmeaHandler.fetchNMEA()
}
}
}
import Foundation
class NMEAhandler: ObservableObject{
#Published var nmea = NMEA()
func fetchNMEA() {
DispatchQueue.global(qos: .userInitiated).async { self.receiveNMEA()
}
}
private func receiveNMEA() {
do {
let path = Bundle.main.path(forResource: "reprex-input", ofType: "txt") ?? "" // file path for file "data.txt"
let message = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
print(message)
message.enumerateLines(invoking: { (line, stop) -> () in
print("Line = \(line)")
})
DispatchQueue.main.async {
self.prepareSentences(data: message)
print("The View updates now.")
}
} catch {
print("Error:", error)
}
}
private func prepareSentences(data: String){
var messages: [String] = []
var fields: [String] = []
if data != "-"{
messages = data.components(separatedBy: "\r\n")
for message in messages{
fields = message.components(separatedBy: ",")
let lastField = fields.last
let splitLast = lastField?.components(separatedBy: "*") ?? [""]
fields.removeLast(1)
fields.append(contentsOf: splitLast)
let valid = checksum(message: message, checksum: fields.last ?? "")
if valid{
parseNMEA(fields: fields)
// The View should be updated by now
}
}
return
}
}
private func checksum(message: String, checksum: String) -> Bool {
let messageLength = message.count
let firstCharacter = message.first
if messageLength < 5 || firstCharacter != "$"{
return false
}
let actualMessage = message.slice(1, messageLength - 4)
var calcChecksum = 0
for c in actualMessage.utf8 {
calcChecksum = calcChecksum ^ Int(c)
}
if calcChecksum == Int(checksum, radix: 16) ?? 999{
return true
}
return false
}
private func parseNMEA(fields: [String]){
switch fields[0].slice(3, 5){
case "MTW":
nmea.mtw.src = fields[0].slice(1, 2)
nmea.mtw.timeStamp = Date()
nmea.mtw.temp = Double(fields[1]) ?? 0
nmea.mtw.units = "ΒΊ" + fields[2]
print("MTW: \(nmea.mtw)")
default:
print("Missing sentence: \(fields[0].slice(1, 2))")
return
}
}
}
import Foundation
extension StringProtocol {
func slice(_ start: Int, _ end: Int) -> String {
if self.count >= end {
let lower = index(self.startIndex, offsetBy: start)
let upper = index(lower, offsetBy: end - start)
return String(self[lower...upper])
} else {
return self as! String
}
}
}
Text file used for input:
$AIMTW,24.7,C*1A
$AIMTW,24.9,C*14
$AIMTW,24.8,C*15
$SDMTW,19.4,C*08
$AIMTW,24.9,C*14
$SDMTW,19.4,C*08
$SDMTW,19.4,C*08
$AIMTW,24.7,C*1A
$SDMTW,19.6,C*0A
$AIMTW,23.9,C*13

Emoji skin-tone detect

Following this post I tried to update the code from Swift 2.0 to Swift 5.0 to check which emojis have skin tones available or not and other variations already present.
My updated code in detail:
extension String {
var emojiSkinToneModifiers: [String] {
return [ "🏻", "🏼", "🏽", "🏾", "🏿" ]
}
var emojiVisibleLength: Int {
var count = 0
enumerateSubstrings(in: startIndex..<endIndex, options: .byComposedCharacterSequences) { (_, _, _, _) in
count = count + 1
}
return count
}
var emojiUnmodified: String {
if self.count == 0 {
return ""
}
let range = String(self[..<self.index(self.startIndex, offsetBy: 1)])
return range
}
var canHaveSkinToneModifier: Bool {
if self.count == 0 {
return false
}
let modified = self.emojiUnmodified + self.emojiSkinToneModifiers[0]
return modified.emojiVisibleLength == 1
}
}
And use this with an array:
let emojis = [ "πŸ‘", "πŸ‘πŸΏ", "🐸" ]
for emoji in emojis {
if emoji.canHaveSkinToneModifier {
let unmodified = emoji.emojiUnmodified
print(emoji)
for modifier in emoji.emojiSkinToneModifiers {
print(unmodified + modifier)
}
} else {
print(emoji)
}
}
The output:
πŸ‘πŸ‘πŸ»πŸ‘πŸΌπŸ‘πŸ½πŸ‘πŸΎπŸ‘πŸΏ πŸ‘πŸΏπŸ‘πŸΏπŸ»πŸ‘πŸΏπŸΌπŸ‘πŸΏπŸ½πŸ‘πŸΏπŸΎπŸ‘πŸΏπŸΏ 🐸🐸🏻🐸🏼🐸🏽🐸🏾🐸🏿
assigns variations to emojis that do not have them or that already is instead of: πŸ‘πŸ‘πŸ»πŸ‘πŸΌπŸ‘πŸ½πŸ‘πŸΎπŸ‘πŸΏ πŸ‘πŸΏ 🐸
I suppose enumerateSubstringsInRange is incorrect and self.characters.count now became self.count easy and correct to count one emoji (composed) compared to before Swift 4 but maybe not useful in this case. What am I not seeing wrong?
Thanks
A "hack" would be to compare the visual representation of a correct emoji (like "🐸") and a wanna-be emoji (like "🐸🏽").
I've modified your code here and there to make it work:
extension String {
static let emojiSkinToneModifiers: [String] = ["🏻", "🏼", "🏽", "🏾", "🏿"]
var emojiVisibleLength: Int {
var count = 0
let nsstr = self as NSString
let range = NSRange(location: 0, length: nsstr.length)
nsstr.enumerateSubstrings(in: range,
options: .byComposedCharacterSequences)
{ (_, _, _, _) in
count = count + 1
}
return count
}
var emojiUnmodified: String {
if isEmpty {
return self
}
let string = String(self.unicodeScalars.first!)
return string
}
private static let emojiReferenceSize: CGSize = {
let size = CGSize(width : CGFloat.greatestFiniteMagnitude,
height: CGFloat.greatestFiniteMagnitude)
let rect = ("πŸ‘" as NSString).boundingRect(with: size,
options: .usesLineFragmentOrigin,
context: nil)
return rect.size
}()
var canHaveSkinToneModifier: Bool {
if isEmpty {
return false
}
let modified = self.emojiUnmodified + String.emojiSkinToneModifiers[0]
let size = (modified as NSString)
.boundingRect(with: CGSize(width : CGFloat.greatestFiniteMagnitude,
height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
context: nil).size
return size == String.emojiReferenceSize
}
}
Let's try it out:
let emojis = [ "πŸ‘", "πŸ‘πŸΏ", "🐸" ]
for emoji in emojis {
if emoji.canHaveSkinToneModifier {
let unmodified = emoji.emojiUnmodified
print(unmodified)
for modifier in String.emojiSkinToneModifiers {
print(unmodified + modifier)
}
} else {
print(emoji)
}
print("\n")
}
And voila!
πŸ‘
πŸ‘πŸ»
πŸ‘πŸΌ
πŸ‘πŸ½
πŸ‘πŸΎ
πŸ‘πŸΏ
πŸ‘
πŸ‘πŸ»
πŸ‘πŸΌ
πŸ‘πŸ½
πŸ‘πŸΎ
πŸ‘πŸΏ
🐸

Swift code to produce a number of possible anagrams from a selected word

I've attempted to research ways to take a given word and calculate the number of possible anagrams a user can make from that word eg an 8 letter word such as snowbanks has 5 eight letter possibilities, 25 seven letter possibilities, etc (those are made up numbers). My initial plan would be to iterate over a dictionary list and check each of the words to see if it is an anagram of the word in question as I've seen suggested in other places.
Rearrange Letters from Array and check if arrangement is in array
seemed very promising, except that it is in objective C and when I tried to convert it to Swift using Swiftify I couldn't get it to work as shown below:
func findAnagrams() -> Set<AnyHashable>? {
let nineCharacters = [unichar](repeating: 0, count: 8)
let anagramKey = self.anagramKey()
// make sure this word is not too long/short.
if anagramKey == nil {
return nil
}
(anagramKey as NSString?)?.getCharacters(nineCharacters, range: NSRange)
let middleCharPos = Int((anagramKey as NSString?)?.range(of: (self as NSString).substring(with: NSRange)).location ?? 0)
var anagrams = Set<AnyHashable>()
// 0x1ff means first 9 bits set: one for each character
for i in 0...0x1ff {
// skip permutations that do not contain the middle letter
if (i & (1 << middleCharPos)) == 0 {
continue
}
var length: Int = 0
var permutation = [unichar](repeating: 0, count: 9)
for bit in 0...9 {
if true {
permutation[length] = nineCharacters[bit]
length += 1
}
}
if length < 4 {
continue
}
let permutationString = String(permutation)
let matchingAnagrams = String.anagramMap()[permutationString] as? [Any]
for word: String? in matchingAnagrams {
anagrams.insert(word)
}
}
return anagrams
}
class func anagramMap() -> [AnyHashable: Any]? {
var anagramMap: [AnyHashable: Any]
if anagramMap != nil {
return anagramMap
}
// this file is present on Mac OS and other unix variants
let allWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
var map = [AnyHashable: Any]()
autoreleasepool {
allWords?.enumerateLines(invoking: {(_ word: String?, _ stop: UnsafeMutablePointer<ObjCBool>?) -> Void in
let key = word?.anagramKey()
if key == nil {
return
}
var keyWords = map[key] as? [AnyHashable]
if keyWords == nil {
keyWords = [AnyHashable]()
map[key] = keyWords
}
if let aWord = word {
keyWords?.append(aWord)
}
})
}
anagramMap = map
return anagramMap
}
func anagramKey() -> String? {
let lowercaseWord = word.lowercased()
// make sure to take the length *after* lowercase. it might change!
let length: Int = lowercaseWord.count
// in this case we're only interested in anagrams 4 - 9 characters long
if length < 3 || length > 9 {
return nil
}
let sortedWord = [unichar](repeating: 0, count: length)
(lowercaseWord as NSString).getCharacters(sortedWord, range: NSRange)
qsort_b(sortedWord, length, MemoryLayout<unichar>.size, {(_ aPtr: UnsafeRawPointer?, _ bPtr: UnsafeRawPointer?) -> Int in
let a = Int(unichar(aPtr))
let b = Int(unichar(bPtr))
return b - a
})
return String(describing: sortedWord)
}
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSMakeRange(0, word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
}
I've also tried the following in an attempt to just produce a list of words that I could iterate over to check for anagrams (I have working code that checks guesses vs the main word to check for anagrams) but I wasn't able to get them to work, possibly because they require a file to be copied to the app, since I was under the impression that the phone has a dictionary preloaded that I could use for words (although I may be mistaken):
var allTheWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
for line: String? in allTheWords?.components(separatedBy: "\n") ?? [String?]() {
print("\(line ?? "")")
print("Double Fail \(allTheWords)")
}
and
if let wordsFilePath = Bundle.main.path(forResource: "dict", ofType: nil) {
do {
let wordsString = try String(contentsOfFile: wordsFilePath)
let wordLines = wordsString.components(separatedBy: NSCharacterSet.newlines)
let randomLine = wordLines[Int(arc4random_uniform(UInt32(wordLines.count)))]
print(randomLine)
} catch { // contentsOfFile throws an error
print("Error: \(error)")
}
}
}
I looked at UIReferenceLibraryViewController as well in an attempt to use it to produce a list of words instead of defining a selected word, but the following isn't a valid option.
let words = UIReferenceLibraryViewController.enumerated
Any assistance would be greatly appreciated!

How to display detected barcode information

I'm creating an iphone app using swift 2. I got the app to be able to read barcodes and display the barcode. Also I learned core data and using it to import a .csv file with a bunch of product information which includes the barcode. Now when a barcode is scanned, I want it to search through the file and display the information relating to it. Here are parts of my code, how will I go about doing this? To be honest, I have programmers block right now and had for the past two days. Any help or guidance will be appreciated.
Here is the CSV Parser:
func parseCSV (contentsOfURL: NSURL, encoding: NSStringEncoding) -> [(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )]? {
// Load the CSV file and parse it
let delimiter = ","
var items:[(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )]?
do {
let content = try String(contentsOfURL: contentsOfURL, encoding: encoding)
print(content)
items = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < textScanner.string.characters.count {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
// For a line without double quotes, we can simply separate the string
// by using the delimiter (e.g. comma)
} else {
values = line.componentsSeparatedByString(delimiter)
}
// Put the values into the tuple and add it to the items array
//(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )
let item = (title: values[0], sku: values[1], price: values[2], weight: values[3], box_length: values[4], box_width: values[5], box_height: values[6], barcodeNum: values[7])
items?.append(item)
}
}
} catch {
print(error)
}
return items
}
When Barcode is detected:
func barcodeDetected(code: String) {
print(code)
if(barcodeDelegate != nil){
barcodeDelegate!.barcodeFound(code)
}
// Let the user know we've found something.
let alert = UIAlertController(title: "Found a Barcode!", message: code, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Search", style: UIAlertActionStyle.Destructive, handler: { action in
// Remove the spaces.
let trimmedCode = code.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// EAN or UPC?
// Check for added "0" at beginning of code.
let trimmedCodeString = "\(trimmedCode)"
var trimmedCodeNoZero: String
if trimmedCodeString.hasPrefix("0") && trimmedCodeString.characters.count > 1 {
trimmedCodeNoZero = String(trimmedCodeString.characters.dropFirst())
// Send the doctored UPC to DataService.searchAPI()
DataService.searchCode(trimmedCodeNoZero)
} else {
// Send the doctored EAN to DataService.searchAPI()
DataService.searchCode(trimmedCodeString)
}
self.navigationController?.popViewControllerAnimated(true)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
Dataservice.swift file
import Foundation
class DataService {
static let dataService = DataService()
static func searchCode(codeNumber: String) {
}
}
I can post more of my project if it helps, but I feel like this is already too much information, anything will help at this point on how to tackle this.

Delete characters in range of string

I have the following string I would like to edit:
var someString = "I wan't this text {something I don't want}"
I would like to remove all the text contained in the two braces, no matter how long that text is. I have been using the follow code to remove a section of a String when I know the range:
extension String {
mutating func deleteCharactersInRange(range: NSRange) {
let mutableSelf = NSMutableString(string: self)
mutableSelf.deleteCharactersInRange(range)
self = mutableSelf
}
}
However, I do not know the range in my problem. Any ideas?
Working with strings and ranges can be quite challenging when mixing NSString and NSRange with Swift's String and Range.
Here is a pure Swift solution.
var someString = "I wan't this text {something I don't want}"
let rangeOpenCurl = someString.rangeOfString("{")
let rangeCloseCurl = someString.rangeOfString("}")
if let startLocation = rangeOpenCurl?.startIndex,
let endLocation = rangeCloseCurl?.endIndex {
someString.replaceRange(startLocation ..< endLocation, with: "")
}
With a RegEx pattern to match anything enclosed with curly brackets:
var sourceString: String = "I wan\'t this text {something I don't want}"
let destinationString = sourceString.stringByReplacingOccurrencesOfString("\\{(.*?)\\}", withString: "", options: .RegularExpressionSearch)
print(destinationString)
This will print "I wan't this text " without the double quotes.
extension String {
func getCurlyBraceRanges() -> [NSRange] {
var results = [NSRange]()
var leftCurlyBrace = -1
for index in 0..<self.characters.count {
let char = self[self.startIndex.advancedBy(index)]
if char == Character("{") {
leftCurlyBrace = index
} else if char == Character("}") {
if leftCurlyBrace != -1 {
results.append(NSRange(location: leftCurlyBrace, length: index - leftCurlyBrace + 1))
leftCurlyBrace = -1
}
}
}
return results
}
mutating func deleteCharactersInRange(range: NSRange) {
let mutableSelf = NSMutableString(string: self)
mutableSelf.deleteCharactersInRange(range)
self = String(mutableSelf)
}
mutating func deleteCharactersInRanges(ranges: [NSRange]) {
var tmpString = self
for i in (0..<ranges.count).reverse() {
tmpString.deleteCharactersInRange(ranges[i])
print(tmpString)
}
self = tmpString
}
}
var testString = "I wan't this text {something I don't want}"
testString.deleteCharactersInRanges(testString.getCurlyBraceRanges())
Output: "I wan't this text "

Resources