Swift: Cannot assign value of type 'String.CharacterView.index' to type 'Int' - swift

Does anyone know why I get the following error when I run my code below?
Error: Cannot assign value of type 'String.CharacterView.index' to type 'Int'
var problem = "find the longest word in the problem description"
var last = problem.characters.last
let lastIndex = problem.characters.endIndex;
print(last);
var words:[String] = [String]();
var word:String = "";
var lastCheck:Int = 0;
for i in problem.characters{
lastCheck = lastCheck + 1
if i != " "{
word = word + String(i)
}
else if lastCheck = lastIndex{
words.append(word);
}
else if i == " "{
words.append(word)
word = ""
}
}
print(words)
UPDATE
I have tried changing the let lastIndex to var lastIndex but no luck

You can't compare lastCheck (an Int) and lastIndex (an Index).
You have to convert it:
var problem = "find the longest word in the problem description"
var last = problem.characters.last
let lastIndex = problem.characters.endIndex;
print(last);
var words:[String] = [String]();
var word:String = "";
var lastCheck:Int = 0;
for i in problem.characters{
lastCheck = lastCheck + 1
let lastIndexInt = problem.characters.startIndex.distanceTo(end: lastIndex) // new
if i != " "{
word = word + String(i)
}
else if lastCheck = lastIndex{
words.append(word);
}
else if i == " "{
words.append(word)
word = ""
}
}
print(words)

Here's a simpler solution to your problem:
let input = "find the longest word in the problem description";
let longest = input
.characters //get the input characters
.split(separator:" ", //split by spaces
maxSplits: 1000, //max number of splits
omittingEmptySubsequences: true) //omit "" splits
.map(String.init) //make new strings from the characters
.max{$0.characters.count < $1.characters.count} //get longest length word
print(longest)
Thanks to originaluser2

String.CharacterView.Index is a struct but not Int. So you cannot assign a Int to your String.CharacterView.Index variable. It's normal.
You have to convert to Int as #Amomchilov's answer, or use Index :
...
var lastCheck:String.CharacterView.Index = problem.startIndex
for i in problem.characters {
lastCheck = lastCheck.advancedBy(1)
...
}
Anyway, in order to find the longest word of a phrase, you can use builtIn function of Swift to get all words then compare their length.
For example:
let str = "find the longest word in the problem description"
var longestWord : String?
str.enumerateSubstringsInRange(str.startIndex..<str.endIndex, options:.ByWords) {
(substring, substringRange, enclosingRange, value) in
if let _subString = substring {
if longestWord == nil || longestWord!.characters.count < _subString.characters.count {
longestWord = _subString
}
}
}

Easy way to get all the words in sentence
var problem = "find the longest word in the problem description"
problem.enumerateSubstrings(in: (problem.range(of: problem))!, options: NSString.EnumerationOptions.byWords, { (substring, substringRange, enclosingRange, stop) -> () in
print(substring)
})
Easy way to get longest word in sentence
var longestWord = ""
problem.enumerateSubstrings(in: (problem.range(of: problem))!, options: NSString.EnumerationOptions.byWords, { (substring, substringRange, enclosingRange, stop) -> () in
if longestWord.characters.count < substring?.characters.count{
longestWord = substring!
}
})
print(longestWord)
Note:Code with reference to Swift 3.For lower version of there will be some syntax changes.Let me know if you need the same for lower versions.

Related

Efficient algorithm to split a string based on multiple string delimiters

I would like to know if there is an efficient way of splitting a string into multiple strings based on delimiters that are also strings.
Eg. updateStr = "23+45 = 56 0" , delimiters = ["+"," ","="]
Result = [23,45,56,0]
I tried the following code in swift:
for i in 0..<delimiter.count {
let res = updateStr.components(separatedBy: delimiter[i])
updateStr = res.joined(separator: "unique%")
}
splitTxt = updateStr.components(separatedBy: "unique%")
This works, but as the delimiters will be received dynamically I want a better approach.
Are there any efficient ways to avoid multiple loops to solve this?
An algorithm with more efficient solution that doesn't involve swift instance methods would also be appreciated.
Thanks for the answers but
To be clearer, I don't just want characters but strings as delimiters:
Eg2. updateStr = "I like playing with friends" , delimiters = ["li"," "la","ie"]
Result = ["I ","ke p","ying with fr","nds"]
The efficient way to do this sort of thing is with a Set:
let equation = "23+45 = 56 0"
let delimiters : [Character] = ["+"," ","="]
let setOfSeparators = Set(delimiters)
let result = equation.split {setOfSeparators.contains($0)}
print(result)
That's efficient because contains on a Set is extremely fast, so that cost is negligible and we are looping implicitly through the original string just once.
On the other hand, you could take advantage of the Cocoa CharacterSet class. For that, I would say:
let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let result = equation.components(separatedBy: characterSet).filter {!$0.isEmpty}
print(result)
Another fun way is to use a Scanner (these are underutilized in my opinion):
let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let scanner = Scanner(string: equation)
var result = [String]()
while let word = scanner.scanUpToCharacters(from: characterSet) {
result.append(word)
scanner.scanCharacters(from: characterSet)
}
print(result)
One of the components(separatedBy:) overloads will handle this automatically using a CharacterSet:
let delimiters = ["+"," ","="].compactMap(UnicodeScalar.init)
let splitTxt = updateStr.components(separatedBy: CharacterSet(delimiters))
NSRegularExpression provides the facility to split on general regular expressions, so this would enable splitting on a finite set of string delimiters using a delim1|delim2|delim3 regex. The following split operation does this job:
static func stringSubrange(str : String, st : Int, en : Int) -> String
{ var result : [Character] = [Character]()
var count : Int = 0
for index in str.indices
{ let c : Character = str[index]
count = count + 1
if count >= st && count <= en
{ result.append(c) }
else if count > en
{ return String(result) }
}
return String(result)
}
static func split(str: String, pattern: String) -> [String]
{ let rge = NSRange(location: 0, length: str.utf16.count)
let regexp = try! NSRegularExpression(pattern: pattern)
let pred = regexp.matches(in: str, options: [], range: rge)
var result : [String] = [String]()
var prev : Int = 1;
for p in pred
{ let range = p.range
let splitString = Ocl.stringSubrange(str: str, st: prev, en: range.location)
prev = range.location + range.length + 1
if splitString.count > 0
{ result.append(splitString) }
}
if prev < str.count
{ result.append(Ocl.stringSubrange(str: str, st: prev, en: str.count)) }
return result
}

How do I count the number of words in a String in Swift?

Say I have a String, how do I determine the number of words in it? I'm trying to create an extension like:
extension String {
var numberOfWords: Int {
// Insert string-counting code here
}
}
If you search "word count string swift" you'll find dozens of StackOverflow answers and gists that tell you to split the string using str.components(separatedBy: " ").count.
DON'T USE components(separatedBy:)!!!
Many non-European languages (particularly East Asian languages) don't use spaces to split words. This will also incorrectly count hyphenated words as separate, and lone punctuation as a word.
The most correct AND most performant way to solve this problem is to use either enumerateSubstrings(in:options:) or CFStringTokenizer.
// enumerateSubstrings
extension String {
var numberOfWords: Int {
var count = 0
let range = startIndex..<endIndex
enumerateSubstrings(in: range, options: [.byWords, .substringNotRequired, .localized], { _, _, _, _ -> () in
count += 1
})
return count
}
}
OR:
// CFStringTokenizer
extension String {
var numberOfWords: Int {
let inputRange = CFRangeMake(0, utf16.count)
let flag = UInt(kCFStringTokenizerUnitWord)
let locale = CFLocaleCopyCurrent()
let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, self as CFString, inputRange, flag, locale)
var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
var count = 0
while tokenType != [] {
count += 1
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
}
return count
}
}
Both are very performant, but enumerateSubtrings(in:options:...) is about twice as fast.
Shocked that nobody is pointing this out elsewhere, so I hope people searching for a solution find this.
Count of words in a string
Create an extension of String
extension String{
var wordCount:Int{
let chararacter = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: chararacter)
let words = comps.filter { !$0.isEmpty }
return words.count
}
}
How to use
"This is a test string".wordCount // Result: 5

Bold words that start with letter

I want to set a label's text with a string that's partly bold. The words I want to make bold all begin with the same letter, say "~".
For example I could have the string, "This ~word is bold, and so is ~this"
Then the label's text would contain the string "This word is bold, and so is this".
Does anybody know if it's possible to make a function like this? I tried the following:
func makeStringBoldForLabel(str: String) {
var finalStr = ""
let words = str.components(separatedBy: " ")
for var word in words {
if word.characters.first == "~" {
var att = [NSFontAttributeName : boldFont]
let realWord = word.substring(from: word.startIndex)
finalStr = finalStr + NSMutableAttributedString(string:realWord, attributes:att)
} else {
finalStr = finalStr + word
}
}
}
but get the error:
Binary operator '+' cannot be applied to operands of type 'String' and 'NSMutableAttributedString'
Easy to solve problem.
use:
func makeStringBoldForLabel(str: String) {
let finalStr = NSMutableAttributedString(string: "")
let words = str.components(separatedBy: " ")
for var word in words {
if word.characters.first == "~" {
var att = [NSFontAttributeName : boldFont]
let realWord = word.substring(from: word.startIndex)
finalStr.append(NSMutableAttributedString(string:realWord, attributes:att))
} else {
finalStr.append(NSMutableAttributedString(string: word))
}
}
}
The error message is clear, you cannot concatenate String and NSAttributedString with the + operator.
You are looking for the API enumerateSubstrings:options. It enumerates strings word by word passing the .byWords option. Unfortunately the tilde (~) is not recognized as a word separator, so we have to check if a word has a preceding tilde. Then change the font attributes at the specific range.
let string = "This ~word is bold, and so is ~this"
let attributedString = NSMutableAttributedString(string: string, attributes:[NSFontAttributeName : NSFont.systemFont(ofSize: 14.0)])
let boldAttribute = NSFont.boldSystemFont(ofSize: 14.0)
string.enumerateSubstrings(in: string.startIndex..<string.endIndex, options: .byWords) { (substring, substringRange, enclosingRange, stop) -> () in
if substring == nil { return }
if substringRange.lowerBound != string.startIndex {
let tildeIndex = string.index(before: substringRange.lowerBound)
if string[tildeIndex..<substringRange.lowerBound] == "~" {
let location = string.distance(from: string.startIndex, to: tildeIndex)
let length = string.distance(from: tildeIndex, to: substringRange.upperBound)
attributedString.addAttribute(NSFontAttributeName, value: boldAttribute, range: NSMakeRange(location, length))
}
}
}

Search multiple words in one string in swift

I have a long string in swift3 and want to check if it contains word1 and word2. It could also be more than 2 search words. I found out following solution:
var Text = "Hello Swift-world"
var TextArray = ["Hello", "world"]
var count = 0
for n in 0..<TextArray.count {
if (Text.contains(TextArray[n])) {
count += 1
}
}
if (count == TextArray.count) {
print ("success")
}
But this seems very complicated, is there not an easier way to solve this? (Xcode8 and swift3)
If you are looking for less code:
let text = "Hello Swift-world"
let wordList = ["Hello", "world"]
let success = !wordList.contains(where: { !text.contains($0) })
print(success)
It is also a little more efficient than your solution because
the contains method returns as soon as a "not contained" word
is found.
As of Swift 4 or later, you can use allSatisfy:
let success = wordList.allSatisfy(text.contains)
A more Swifty solution that will stop searching after it found a non-existent word:
var text = "Hello Swift-world"
var textArray = ["Hello", "world"]
let match = textArray.reduce(true) { !$0 ? false : (text.range(of: $1) != nil ) }
Another way to do it which stops after it found a non-match:
let match = textArray.first(where: { !text.contains($0) }) == nil
Another possibility is regular expressions:
// *'s are wildcards
let regexp = "(?=.*Hello*)(?=.*world*)"
if let range = Text.range(of:regexp, options: .regularExpression) {
print("this string contains Hello world")
} else {
print("this string doesn't have the words we want")
}

How to increment String in Swift

I need to save files in an alphabetical order.
Now my code is saving files in numeric order
1.png
2.png
3.png ...
The problem is when i read this files again I read this files as described here
So I was thinking of changing the code and to save the files not in a numeric order but in an alphabetical order as:
a.png b.png c.png ... z.png aa.png ab.png ...
But in Swift it's difficult to increment even Character type.
How can I start from:
var s: String = "a"
and increment s in that way?
You can keep it numeric, just use the right option when sorting:
let arr = ["1.png", "19.png", "2.png", "10.png"]
let result = arr.sort {
$0.compare($1, options: .NumericSearch) == .OrderedAscending
}
// result: ["1.png", "2.png", "10.png", "19.png"]
If you'd really like to make them alphabetical, try this code to increment the names:
/// Increments a single `UInt32` scalar value
func incrementScalarValue(_ scalarValue: UInt32) -> String {
return String(Character(UnicodeScalar(scalarValue + 1)))
}
/// Recursive function that increments a name
func incrementName(_ name: String) -> String {
var previousName = name
if let lastScalar = previousName.unicodeScalars.last {
let lastChar = previousName.remove(at: previousName.index(before: previousName.endIndex))
if lastChar == "z" {
let newName = incrementName(previousName) + "a"
return newName
} else {
let incrementedChar = incrementScalarValue(lastScalar.value)
return previousName + incrementedChar
}
} else {
return "a"
}
}
var fileNames = ["a.png"]
for _ in 1...77 {
// Strip off ".png" from the file name
let previousFileName = fileNames.last!.components(separatedBy: ".png")[0]
// Increment the name
let incremented = incrementName(previousFileName)
// Append it to the array with ".png" added again
fileNames.append(incremented + ".png")
}
print(fileNames)
// Prints `["a.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png", "k.png", "l.png", "m.png", "n.png", "o.png", "p.png", "q.png", "r.png", "s.png", "t.png", "u.png", "v.png", "w.png", "x.png", "y.png", "z.png", "aa.png", "ab.png", "ac.png", "ad.png", "ae.png", "af.png", "ag.png", "ah.png", "ai.png", "aj.png", "ak.png", "al.png", "am.png", "an.png", "ao.png", "ap.png", "aq.png", "ar.png", "as.png", "at.png", "au.png", "av.png", "aw.png", "ax.png", "ay.png", "az.png", "ba.png", "bb.png", "bc.png", "bd.png", "be.png", "bf.png", "bg.png", "bh.png", "bi.png", "bj.png", "bk.png", "bl.png", "bm.png", "bn.png", "bo.png", "bp.png", "bq.png", "br.png", "bs.png", "bt.png", "bu.png", "bv.png", "bw.png", "bx.png", "by.png", "bz.png"]`
You will eventually end up with
a.png
b.png
c.png
...
z.png
aa.png
ab.png
...
zz.png
aaa.png
aab.png
...
Paste this code in the playground and check result. n numbers supported means you can enter any high number such as 99999999999999 enjoy!
you can uncomment for loop code to check code is working fine or not
but don't forget to assign a lesser value to counter variable otherwise Xcode will freeze.
var fileName:String = ""
var counter = 0.0
var alphabets = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let totalAlphaBets = Double(alphabets.count)
let numFiles = 9999
func getCharacter(counter c:Double) -> String {
var chars:String
var divisionResult = Int(c / totalAlphaBets)
let modResult = Int(c.truncatingRemainder(dividingBy: totalAlphaBets))
chars = getCharFromArr(index: modResult)
if(divisionResult != 0){
divisionResult -= 1
if(divisionResult > alphabets.count-1){
chars = getCharacter(counter: Double(divisionResult)) + chars
}else{
chars = getCharFromArr(index: divisionResult) + chars
}
}
return chars
}
func getCharFromArr(index i:Int) -> String {
if(i < alphabets.count){
return alphabets[i]
}else{
print("wrong index")
return ""
}
}
for _ in 0...numFiles {
fileName = getCharacter(counter: counter)+".png"
print(fileName)
counter += 1
}
fileName = getCharacter(counter: Double(numFiles))+".png"
print(fileName)