Fastest way to convert Character to uppercase or lowercase in Swift 4? - swift

I understand the reasons why the Character class doesn't support toUpper() and toLower() but my use case is not for language purposes. Furthermore, I do not wish to revert to NSString.
So what's the fastest way to convert a character to upper case or lower case using Swift 4?
// Is there something better than this?
extension Character {
func toLower() -> Character {
return String(self).lowercased().first!
}
}

Use the uppercase2() below if you only need to uppercase the first char. It’s a 5x speed up over uppercasing the entire string.
import Foundation
// too slow, maybe with some bitwise operations could get faster 🤷‍♀️
func uppercase(_ string: String) -> Character? {
let key: Int8 = string.utf8CString[0]
guard key>0, key<127, let c = Unicode.Scalar(Int(key >= 97 ? key - Int8(32) : key)) else { return nil }
return Character(c)
}
// winner but using internal _core stuff
func uppercase2(_ string: String) -> Character? {
guard let key = string._core.asciiBuffer?[0] else { return nil }
return Character(Unicode.Scalar(key >= 97 ? key - 32 : key)) // use < + to lowercase
}
func measure(times: Int, task: ()->()){
let start1 = CFAbsoluteTimeGetCurrent()
for _ in 1..<times {
task()
}
print(CFAbsoluteTimeGetCurrent() - start1)
}
print("😀".uppercased().first as Any) // Optional("😀")
print(uppercase("😀") as Any) // nil
print(uppercase2("😀") as Any) // nil
measure(times: 10_000_000) { _ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".uppercased().first } // 4.17883902788162
measure(times: 10_000_000) { _ = uppercase("ABCDEFGHIJKLMNOPQRSTUVWXYZ") } // 4.91275697946548
measure(times: 10_000_000) { _ = uppercase2("ABCDEFGHIJKLMNOPQRSTUVWXYZ") } // 0.720575034618378
In a 10 million run, Apple’s uppercased ran 148x times faster than the code at the bottom of this post, even with force-unwrap. I’ll leave it for comedic purposes.
Their approach is of course, way lower level. See lowercased(). They check for an internal asciiBuffer and then use an _asciiUpperCaseTable.
My understanding is that if the original String is already a Swift String, it will be represented by a StringCore class which is already optimized to deal with ASCII characters at a low level. Thus, you won’t be able to beat the uppercase function of Swift.
So, kind of an answer: the fastest way is to use the regular uppercase() function.
I'm assuming that “my use-case is not for language purposes” means I’m only using ASCII. The advantage that this provides is that UTF-8 and ASCII share the same scalar code, so upper/lowercasing implies subtracting or adding a fixed number.
import Foundation
print("a".unicodeScalars.first!.value) // 97
print("A".unicodeScalars.first!.value) // 65
let uppercase = String("abcde".flatMap {
guard let char = $0.unicodeScalars.first,
let uppercased = Unicode.Scalar(char.value - UInt32(97 - 65))
else {
return nil
}
return Character(uppercased)
})
print(uppercase) // ABCDE

Related

Count number of characters between two specific characters

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.

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.

Swift: Identify the entered characters are in ascending/descending sequence

I have a textfield where i can't set the text which is ascending/descending sequence. like "abcdefgh" or "1234567". how to identify the entered string is not in ascending/descending order in swift.
This should do the trick:
enum Order {
case ascending
case descending
case none
}
func getOrder(of text: String) -> Order {
switch text {
case String(text.sorted()):
return .ascending
case String(text.sorted().reversed()):
return .descending
default:
return .none
}
}
Others have proposed sorting the string and checking to see if the array changes with sorting. That's not quite right, since the string "abcxyz" is in sorted order, but is not a sequence because it's missing some characters. Sorting is also relatively expensive, at least on larger arrays. (Not an issue if you're dealing with a few hundred characters, since a good sort algorithm's O(n•log n) performance is pretty much O(n) performance for small data-sets.)
How about this extension on String I came up with:
extension String {
func isSequence() -> Bool
{
let charValues = Array(self.unicodeScalars).map { $0.value }
guard let first = charValues.first,
let last = charValues.last else { return false }
return charValues == Array(first...last)
}
}
It maps the string into an array of UTF32 values, and then checks to see if the result is the same as a range from first...last of the values.
You could use that function to validate a user's text input to see if it is a continuous sequence of characters like "abcdefg".
The above should have O(n) time complexity.
You could do it this way:
func isOrderedSequence(_ string: String) -> Bool {
return string == String(string.sorted())
}
Here are some test cases:
isOrderedSequence("1234567") //true
isOrderedSequence("7836") //false
isOrderedSequence("abcdefg") //true
isOrderedSequence("Hello") //false
isOrderedSequence("123abc") //true
It uses sorted()

How can I check if a string contains Chinese in Swift?

I want to know that how can I check if a string contains Chinese in Swift?
For example, I want to check if there's Chinese inside:
var myString = "Hi! 大家好!It's contains Chinese!"
Thanks!
This answer
to How to determine if a character is a Chinese character can also easily be translated from
Ruby to Swift (now updated for Swift 3):
extension String {
var containsChineseCharacters: Bool {
return self.range(of: "\\p{Han}", options: .regularExpression) != nil
}
}
if myString.containsChineseCharacters {
print("Contains Chinese")
}
In a regular expression, "\p{Han}" matches all characters with the
"Han" Unicode property, which – as I understand it – are the characters
from the CJK languages.
Looking at questions on how to do this in other languages (such as this accepted answer for Ruby) it looks like the common technique is to determine if each character in the string falls in the CJK range. The ruby answer could be adapted to Swift strings as extension with the following code:
extension String {
var containsChineseCharacters: Bool {
return self.unicodeScalars.contains { scalar in
let cjkRanges: [ClosedInterval<UInt32>] = [
0x4E00...0x9FFF, // main block
0x3400...0x4DBF, // extended block A
0x20000...0x2A6DF, // extended block B
0x2A700...0x2B73F, // extended block C
]
return cjkRanges.contains { $0.contains(scalar.value) }
}
}
}
// true:
"Hi! 大家好!It's contains Chinese!".containsChineseCharacters
// false:
"Hello, world!".containsChineseCharacters
The ranges may already exist in Foundation somewhere rather than manually hardcoding them.
The above is for Swift 2.0, for earlier, you will have to use the free contains function rather than the protocol extension (twice):
extension String {
var containsChineseCharacters: Bool {
return contains(self.unicodeScalars) {
// older version of compiler seems to need extra help with type inference
(scalar: UnicodeScalar)->Bool in
let cjkRanges: [ClosedInterval<UInt32>] = [
0x4E00...0x9FFF, // main block
0x3400...0x4DBF, // extended block A
0x20000...0x2A6DF, // extended block B
0x2A700...0x2B73F, // extended block C
]
return contains(cjkRanges) { $0.contains(scalar.value) }
}
}
}
The accepted answer only find if string contains Chinese character, i created one suit for my own case:
enum ChineseRange {
case notFound, contain, all
}
extension String {
var findChineseCharacters: ChineseRange {
guard let a = self.range(of: "\\p{Han}*\\p{Han}", options: .regularExpression) else {
return .notFound
}
var result: ChineseRange
switch a {
case nil:
result = .notFound
case self.startIndex..<self.endIndex:
result = .all
default:
result = .contain
}
return result
}
}
if "你好".findChineseCharacters == .all {
print("All Chinese")
}
if "Chinese".findChineseCharacters == .notFound {
print("Not found Chinese")
}
if "Chinese你好".findChineseCharacters == .contain {
print("Contains Chinese")
}
gist here: https://gist.github.com/williamhqs/6899691b5a26272550578601bee17f1a
Try this in Swift 2:
var myString = "Hi! 大家好!It's contains Chinese!"
var a = false
for c in myString.characters {
let cs = String(c)
a = a || (cs != cs.stringByApplyingTransform(NSStringTransformMandarinToLatin, reverse: false))
}
print("\(myString) contains Chinese characters = \(a)")
I have created a Swift 3 String extension for checking how much Chinese characters a String contains. Similar to the code by Airspeed Velocity but more comprehensive. Checking various Unicode ranges to see whether a character is Chinese. See Chinese character ranges listed in the tables under section 18.1 in the Unicode standard specification: http://www.unicode.org/versions/Unicode9.0.0/ch18.pdf
The String extension can be found on GitHub: https://github.com/niklasberglund/String-chinese.swift
Usage example:
let myString = "Hi! 大家好!It contains Chinese!"
let chinesePercentage = myString.chinesePercentage()
let chineseCharacterCount = myString.chineseCharactersCount()
print("String contains \(chinesePercentage) percent Chinese. That's \(chineseCharacterCount) characters.")

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")
}