4

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.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
christophriepe
  • 1,157
  • 12
  • 47
  • 1
    Does letter includes non standard letters or only "A"..."Z"? Does "Ç" should be considered a letter? – Leo Dabus Dec 18 '20 at 16:36
  • @LeoDabus Only Standard Letters "A" to "Z". Special Letters like "Ç" as well as Numbers and Special Chars should pass Filtering. – christophriepe Dec 18 '20 at 16:37
  • @LeoDabus "Ç" is a standard letter: it's in Unicode standard, and the standard recognizes it as a letter. There are also lots of standard letters in the Greek or the Cyrillic alphabet. I suppose that with "non-standard" you mean "[non roman](https://en.wikipedia.org/wiki/Latin_alphabet)" ? ;-) – Christophe Jan 01 '22 at 18:59
  • @Christophe that’s why I asked. Ç is a letter but it is not in the English alphabet – Leo Dabus Jan 01 '22 at 20:50
  • 1
    @LeoDabus Sorry, no offense. I even upvoted your interesting answer. I tease you on the terminology. "Standard alphabet" is not "English alphabet" except for 379 millions of [English-native speakers](https://en.wikipedia.org/wiki/List_of_languages_by_number_of_native_speakers) among 7,9 billions of [people in the world](https://www.live-counter.com/world-population/). So 96% of the world population have another understanding of "standard alphabet" ;-) By the way, English alphabet is not even English, it is roman/latin ;-) – Christophe Jan 01 '22 at 21:47

6 Answers6

3

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"}]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thanks for your Answer. What exactly is the ~= Operator? – christophriepe Dec 18 '20 at 16:41
  • In this case it is the same as contains. It is called pattern matching operator. This can also be written as `{!("A"..."Z").contains(($0.name.first?.uppercased() ?? "#")) }` – Leo Dabus Dec 18 '20 at 16:42
2

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.

Christophe
  • 68,716
  • 7
  • 72
  • 138
0

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")
}```
Drian La
  • 5
  • 3
0

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
Arthur Josselin
  • 197
  • 1
  • 2
  • 9
0

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 }
Palle
  • 11,511
  • 2
  • 40
  • 61
  • 1
    `isLetter` will include undesired letters as well. That's why I've posted a comment asking it at first place. https://developer.apple.com/documentation/swift/character/3127011-isletter – Leo Dabus Dec 18 '20 at 17:30
0

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

Md. Ibrahim Hassan
  • 5,359
  • 1
  • 25
  • 45