How to Check if String begins with Alphabet Letter in Swift 5?

Viewed 1853

Problem: i am currently trying to Sort a List in SwiftUI according to the Items First Character. I also would like to implement a Section for all Items, which doesn't begin with a Character of the Alphabet (Numbers, Special Chars).

My Code so far:

let nonAlphabetItems = items.filter { $0.name.uppercased() != /* beginns with A - Z */ }

Does anyone has a Solution for this Issue. Of course I could do a huge Loop Construct, however I hope there is a more elegant way.

Thanks for your help.

6 Answers

You can check if a string range "A"..."Z" contains the first letter of your name property:

struct Item {
    let name: String
}

let items: [Item] = [.init(name: "Def"),.init(name: "Ghi"),.init(name: "123"),.init(name: "Abc")]

let nonAlphabetItems = items.filter { !("A"..."Z" ~= ($0.name.first?.uppercased() ?? "#")) }

nonAlphabetItems  // [{name "123"}]

Expanding on this topic we can extend Character to add a isAsciiLetter property:

extension Character {
    var isAsciiLetter: Bool { "A"..."Z" ~= self || "a"..."z" ~= self }
}

This would allow to extend StringProtocol to check is a string starts with an ascii letter:

extension StringProtocol {
    var startsWithAsciiLetter: Bool { first?.isAsciiLetter == true }
}

And just a helper to negate a boolean property:

extension Bool {
    var negated: Bool { !self }
}

Now we can filter the items collection as follow:

let nonAlphabetItems = items.filter(\.name.startsWithAsciiLetter.negated)   // [{name "123"}]

If you need an occasional filter, you could simply write a condition combining standard predicates isLetter and isASCII which are already defined for Character. It's as simple as:

let items = [ "Abc", "01bc", "Ça va", "", "  ", ""]
let nonAlphabetItems = items.filter { $0.isEmpty || !$0.first!.isASCII || !$0.first!.isLetter }
print (nonAlphabetItems)  // -> Output:  ["01bc", "Ça va", "", "  ", ""]

If the string is not empty, it has for sure a first character $0.first!. It is tempting to use isLetter , but it appears to be true for many characters in many local alphabets, including for example the antique Egyptian hieroglyphs like "" or the French alphabet with "Ç"and accented characters. This is why you need to restrict it to ASCII letters, to limit yourself to the roman alphabet.

You can use NSCharacterSet in the following way :


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

You can deal with ascii value

extension String {
    var fisrtCharacterIsAlphabet: Bool {
         guard let firstChar = self.first else { return false }
         let unicode = String(firstChar).unicodeScalars
         let ascii = Int(unicode[unicode.startIndex].value)
         return (ascii >= 65 && ascii <= 90) || (ascii >= 97 && ascii <= 122)
    }
    
}

var isAlphabet = "Hello".fisrtCharacterIsAlphabet

The Character type has a property for this:

let x: Character = "x"
x.isLetter // true for letters, false for punctuation, numbers, whitespace, ...

Note that this will include characters from other alphabets (Greek, Cyrillic, Chinese, ...).

As String is a Sequence with Element equal to Character, we can use the .first property to get the first char.

With this, you can filter your items:

let filtered = items.filter { $0.name.first?.isLetter ?? false }

You can get this done through this simple String extension

extension StringProtocol {
    var isFirstCharacterAlp: Bool {
        first?.isASCII == true && first?.isLetter == true
    }
}

Usage:

print ("H1".isFirstCharacterAlp)
print ("ابراهيم1".isFirstCharacterAlp)

Output

true
false

Happy Coding!

Reference

Related