Count number of characters between two specific characters - swift

Trying to make a func that will count characters in between two specified char like:
count char between "#" and "." or "#" and ".com"
If this is only solution could this code be written in a simple way with .count or something less confusing
func validateEmail(_ str: String) -> Bool {
let range = 0..<str.count
var numAt = Int()
numDot = Int()
if str.contains("#") && str.contains(".") && str.characters.first != "#" {
for num in range {
if str[str.index(str.startIndex, offsetBy: num)] == "#" {
numAt = num
print("The position of # is \(numAt)")
} else if
str[str.index(str.startIndex, offsetBy: num)] == "." {
numDot = num
print("The position of . is \(numDot)")
}
}
if (numDot - numAt) > 1 {
return true
}
}
return false
}

With help from #Βασίλης Δ. i made a direct if statement for func validateEmail that check if number of char in between are less than 1
if (str.split(separator: "#").last?.split(separator: ".").first!.count)! < 1{
return false
}
It could be usefull

There are many edge cases to what you're trying to do, and email validation is notoriously complicated. I recommend doing as little of it as possible. Many, many things are legal email addresses. So you will need to think carefully about what you want to test. That said, this addresses what you've asked for, which is the distance between the first # and the first . that follows it.
func lengthOfFirstComponentAfterAt(in string: String) -> Int? {
guard
// Find the first # in the string
let firstAt = string.firstIndex(of: "#"),
// Find the first "." after that
let firstDotAfterAt = string[firstAt...].firstIndex(of: ".")
else {
return nil
}
// Return the distance between them (not counting the dot itself)
return string.distance(from: firstAt, to: firstDotAfterAt) - 1
}
lengthOfFirstComponentAfterAt(in: "rob#example.org") // Optional(7)
There's a very important lesson about Collections in this code. Notice the expression:
string[firstAt...].firstIndex(of: ".")
When you subscript a Collection, each element of the resulting slice has the same index as in the original collection. The returned value from firstIndex can be used directly to subscript string without offsetting. This is very different than how indexes work in many other languages, and allows powerful algorithms, and also creates at lot of bugs when developers forget this.

Related

Check for String containing letters to return -1 in Swift command line app

I have been at this for hours and can't work it out, please help. I am doing some coding practice to help sharpen up my skills in swift and this one seems so easy but I can't work it out.
I need to create a simple function that returns (the challenge i'm doing asks for this I haven't made it up) the sum of numbers as a string, but if the string contains characters, not numbers, it should return -1. It says : Receive two values of type string. Add them together. If an input is a character, return -1
This is where I am up to but i can't get it pass the tests for returning -1. It passes 3 / 5 tests where it's fine with numbers, but not with the characters. My thinking is that the character set line should check for if myNewString contains any of those characters it should return -1
func addStrNums(_ num1: String, _ num2: String) -> String {
// write your code here
var op1 = num1
var op2 = num2
var total: Int = 0
var myNewInt = Int(op1) ?? 0
var myNewInt2 = Int(op2) ?? 0
total = myNewInt + myNewInt2
var myNewString = String (total)
let characterset = CharacterSet(charactersIn:
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#£$%^&*()_-=+;/?><"
)
if myNewString.rangeOfCharacter(from: characterset) != nil {
return "-1"
}
return myNewString
}
Results of above is :
Test Passed: 10 == 10
FAILED: Expected: -1, instead got: 5
Test Passed: 1 == 1
Test Passed: 3 == 3
import foundation
func addStringNumber(_ num1: String, num2: String) -> String {
if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: num1)) {
return "-1"
}
if !CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: num2)) {
return ""
}
let sum = String.init(format: "%d", Int((num1 as NSString).intValue + (num2 as NSString).intValue))
return sum
}
Note:- for this import foundation is important.
A couple of thoughts:
You are using nil coalescing operator (??), which effectively says that you want to ignore errors parsing the integers. I would suggest that you want to detect those errors. E.g.
func addStrNums(_ num1: String, _ num2: String) -> String {
guard
let value1 = Int(num1),
let value2 = Int(num2)
else {
return "-1"
}
let total = value1 + value2
return "\(total)"
}
The initializer for Int will automatically fail if there are non-numeric characters.
These programming tests generally don’t worry about localization concerns, but in a real app, we would almost never use this Int string-to-integer conversion (because we want to honor the localization settings of the user’s device). For this reason, we would generally prefer NumberFormatter in real apps for users with different locales:
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
func addStrNums(_ num1: String, _ num2: String) -> String {
guard
let value1 = formatter.number(from: num1)?.intValue, // use `doubleValue` if you want to handle floating point numbers, too
let value2 = formatter.number(from: num2)?.intValue
else {
return "-1"
}
let total = value1 + value2
return formatter.string(for: total) ?? "-1"
}
This accepts input that includes thousands separators and formats the output accordingly. (Obviously, feel free to use whatever numberStyle you want.)
Also, be aware that NumberFormatter is more tolerant of whitespace before or after the digits. It also allows for a wider array of numeric characters, e.g. it recognizes “5”, a digit entered with Japanese keyboard, which is different than the standard ASCII “5”. This greater flexibility is important in a real app, but I don’t know what your programming exercise is requiring.
While the above demonstrates that you don’t have to check for non-numeric digits manually, you can if you need to. But you need to check the two input strings, not the string representation of the total (especially if you used nil coalescing operator to disregard errors when converting the strings to integers, as I discussed in point 1 above).
If you do this, though, I would not advise trying list all of the non-numeric characters yourself. Instead, use the inverted set:
let characterSet = CharacterSet(charactersIn: "0123456789").inverted // check for 0-9 if using `Int` To convert, use `CharacterSet.decimalDigits` if converting with `NumberFormatter`
guard
num1.rangeOfCharacter(from: characterSet) == nil,
num2.rangeOfCharacter(from: characterSet) == nil
else {
return "-1"
}
Or, one could use regex to confirm that there are only one or more digits (\d+) between the start of the string (^) and the end of the string ($):
guard
num1.range(of: #"^\d+$"#, options: .regularExpression) != nil, // or `#"^-?\d+$"#` if you want to accept negative values, too
num2.range(of: #"^\d+$"#, options: .regularExpression) != nil
else {
return "-1"
}
FWIW, if you’re not familiar with it, the #"..."# syntax employs “extended string delimiters” (saving us from having to to escape the \ characters within the string).
As an aside, you mentioned a command line app that should return -1. Generally when we talk about command line apps returning values, we’re exiting with a numeric value not a string. I would be inclined to make this function return an Int?, i.e. either the numeric sum or nil on failure. But without seeing the particulars on your coding test, it is hard to be more specific.

Optimizing adding dashes to a long Swift String

I am trying to take a hex string and insert dashes between every other character (e.g. "b201a968" to "b2-01-a9-68"). I have found several ways to do it, but the problem is my string is fairly large (8066 characters) and the fastest I can get it to work it still takes several seconds. These are the ways I have tried and how long they are taking. Can anyone help me optimize this function?
//42.68 seconds
func reformatDebugString(string: String) -> String
{
var myString = string
var index = 2
while(true){
myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
index += 3
if(index >= myString.characters.count){
break
}
}
return myString
}
//21.65 seconds
func reformatDebugString3(string: String) -> String
{
var myString = ""
let length = string.characters.count
var first = true
for i in 0...length-1{
let index = string.index(myString.startIndex, offsetBy: i)
let c = string[index]
myString += "\(c)"
if(!first){
myString += "-"
}
first = !first
}
return myString
}
//11.37 seconds
func reformatDebugString(string: String) -> String
{
var myString = string
var index = myString.characters.count - 2
while(true){
myString.insert("-", at: myString.index(myString.startIndex, offsetBy: index))
index -= 2
if(index == 0){
break
}
}
return myString
}
The problem with all three of your approaches is the use of index(_:offsetBy:) in order to get the index of the current character in your loop. This is an O(n) operation where n is the distance to offset by – therefore making all three of your functions run in quadratic time.
Furthermore, for solutions #1 and #3, your insertion into the resultant string is an O(n) operation, as all the characters after the insertion point have to be shifted up to accommodate the added character. It's generally cheaper to build up the string from scratch in this case, as we can just add a given character onto the end of the string, which is O(1) if the string has enough capacity, O(n) otherwise.
Also for solution #1, saying myString.characters.count is an O(n) operation, so not something you want to be doing at each iteration of the loop.
So, we want to build the string from scratch, and avoid indexing and calculating the character count inside the loop. Here's one way of doing that:
extension String {
func addingDashes() -> String {
var result = ""
for (offset, character) in characters.enumerated() {
// don't insert a '-' before the first character,
// otherwise insert one before every other character.
if offset != 0 && offset % 2 == 0 {
result.append("-")
}
result.append(character)
}
return result
}
}
// ...
print("b201a968".addingDashes()) // b2-01-a9-68
Your best solution (#3) in a release build took 37.79s on my computer, the method above took 0.023s.
As already noted in Hamish's answer, you should avoid these two things:
calculate each index with string.index(string.startIndex, offsetBy: ...)
modifying a large String with insert(_:at:)
So, this can be another way:
func reformatDebugString4(string: String) -> String {
var result = ""
var currentIndex = string.startIndex
while currentIndex < string.endIndex {
let nextIndex = string.index(currentIndex, offsetBy: 2, limitedBy: string.endIndex) ?? string.endIndex
if currentIndex != string.startIndex {
result += "-"
}
result += string[currentIndex..<nextIndex]
currentIndex = nextIndex
}
return result
}

How to I get the first or last few characters of a string in a method chain?

If I use functional-style method chains for string manipulation, I can not use the usual machinery for getting the first or last few characters: I do not have access to a reference to the current string, so I can not compute indices.
Example:
[some, nasty, objects]
.map( { $0.asHex } )
.joined()
.<first 100>
.uppercased()
+ "..."
for a truncated debug output.
So how to I implement <first 100>, or do I have to break the chain?
I don't know of any API that does this. Fortunately, writing our own is an easy exercise:
extension String {
func taking(first: Int) -> String {
if first <= 0 {
return ""
} else if let to = self.index(self.startIndex,
offsetBy: first,
limitedBy: self.endIndex) {
return self.substring(to: to)
} else {
return self
}
}
}
Taking from the end is similar.
Find full code (including variants) and tests here.

Swift - endIndex.predecessor()

Do these comments make any sense ?
Trying to figure out why it removes the character of my reversed given string, instead of the regular given string in this case.
import Foundation
extension String {
func reverseWords() -> String {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
print("..\(result)..")
// Result is the words array ( contains self ) reversed with seperator " "
print("..\(self)..")
result.removeAtIndex(self.endIndex.predecessor())
// So here i am checking self within result?, and am removing the last index
// of my currently reversed given string inside result?
// I do result in my intended last space removal with result.endIndex but i'm
// wondering what happens in this case with the self.endIndex :)
return result
}
}
var str = "This string contains a few elements"
str.reverseWords()
self still refers to the original unreversed String.
The correct code would be:
result.removeAtIndex(result.endIndex.predecessor())
You should never use a String index for another string. If you are indexing result, you shouldn't use an index from self.
With a simple string you won't seem a difference but if you start adding multi-byte characters, e.g. emojis, your application can crash.
For example, using result += thisWord + "😀" would result in:
elements😀few😀a😀contains😀string�This😀
with self.endIndex
and
elements😀few😀a😀contains😀string😀This
with result.endIndex.
endIndex is the index past the end of the String. It works the same as count for arrays. count in arrays doesn't represent the last element, count - 1 represents the last element.
If your aim is to change the original String, you have to declare the method as mutating and assign to self, e.g.:
mutating func reverseWords() {
var result = ""
let words = self.componentsSeparatedByString(" ")
for thisWord in words.reverse() {
result += thisWord + " "
}
self = result
self.removeAtIndex(self.endIndex.predecessor())
}
although that's rather uncommon in functional programming.
Using self.endIndex.predecessor() to remove from result won't effect self (the "given" String).
The index is just an abstraction of a number. That number is based off the string self, rather than result
But ultimately, that index is used to remove from result, not from self.
So this is what's happening, which indeed results into the conclusion that it's quite idiotic to use the index of self.. ?
result.removeAtIndex(self.endIndex.predecessor()) // <-- Stupid move
// self.endIndex = 35
// self.endIndex predecessor = 34 ( last index in self )
// Removes index 34 out of result
// result.endIndex = 36
// results.endIndex predecessor = 35 ( the " " )
// Removes the one before the " " inside results

How can I check if a string contains letters in Swift? [duplicate]

This question already has answers here:
What is the best way to determine if a string contains a character from a set in Swift
(11 answers)
Closed 7 years ago.
I'm trying to check whether a specific string contains letters or not.
So far I've come across NSCharacterSet.letterCharacterSet() as a set of letters, but I'm having trouble checking whether a character in that set is in the given string. When I use this code, I get an error stating:
'Character' is not convertible to 'unichar'
For the following code:
for chr in input{
if letterSet.characterIsMember(chr){
return "Woah, chill out!"
}
}
You can use NSCharacterSet in the following way :
let letters = NSCharacterSet.letters
let phrase = "Test case"
let range = phrase.rangeOfCharacter(from: characterSet)
// range will be nil if no letters is found
if let test = range {
println("letters found")
}
else {
println("letters not found")
}
Or you can do this too :
func containsOnlyLetters(input: String) -> Bool {
for chr in input {
if (!(chr >= "a" && chr <= "z") && !(chr >= "A" && chr <= "Z") ) {
return false
}
}
return true
}
In Swift 2:
func containsOnlyLetters(input: String) -> Bool {
for chr in input.characters {
if (!(chr >= "a" && chr <= "z") && !(chr >= "A" && chr <= "Z") ) {
return false
}
}
return true
}
It's up to you, choose a way. I hope this help you.
You should use the Strings built in range functions with NSCharacterSet rather than roll your own solution. This will give you a lot more flexibility too (like case insensitive search if you so desire).
let str = "Hey this is a string"
let characterSet = NSCharacterSet(charactersInString: "aeiou")
if let _ = str.rangeOfCharacterFromSet(characterSet, options: .CaseInsensitiveSearch) {
println("true")
}
else {
println("false")
}
Substitute "aeiou" with whatever letters you're looking for.
A less flexible, but fun swift note all the same, is that you can use any of the functions available for Sequences. So you can do this:
contains("abc", "c")
This of course will only work for individual characters, and is not flexible and not recommended.
The trouble with .characterIsMember is that it takes a unichar (a typealias for UInt16).
If you iterate your input using the utf16 view of the string, it will work:
let set = NSCharacterSet.letterCharacterSet()
for chr in input.utf16 {
if set.characterIsMember(chr) {
println("\(chr) is a letter")
}
}
You can also skip the loop and use the contains algorithm if you only want to check for presence/non-presence:
if contains(input.utf16, { set.characterIsMember($0) }) {
println("contains letters")
}