39

This is what I have so far (for testing purpose):

let string = "The quick BroWn fOX jumpS Over tHe lazY DOg"

for chr in string {
    if isupper(String(chr)) {
        print(String(chr).lowercaseString)
        continue
    }
    print(chr)
}

how can I test for uppercase and lowercase characters?

I know I can call C functions from swift, but this does not seems to be correct for me. How can I do this with swift only?

yeyo
  • 2,954
  • 2
  • 29
  • 40
  • What do you want to do with the result? Get a string containing all uppercase characters? – Joachim Isaksson Jun 17 '14 at 16:16
  • No. I want to know how can I test for uppercase and lowercase characters. I provided an example for completeness sake. – yeyo Jun 17 '14 at 16:20
  • 2
    Whats not right about this? the `isupper` call? If you just want to know if there is upper case in the string, maybe `if string.lowercaseString == string`? – Jack Jun 17 '14 at 16:24

18 Answers18

21

I am not sure what you mean by trying to avoid C functions. I hope this does not include avoiding the frameworks and foundational libraries that OS X and iOS offer you when developing an app, like, for instance, the NSCharacterSet class which provides exactly what you need in a Unicode compatible implementation.

To expand on Matt's answer, bringing it a little closer to your question's requirements:

import UIKit

let testString = "Åke röstet un café in Владивосток!"
let lowerCase = NSCharacterSet.lowercaseLetterCharacterSet()
let upperCase = NSCharacterSet.uppercaseLetterCharacterSet()

for currentCharacter in testString.utf16 {
  if lowerCase.characterIsMember(currentCharacter) {
    println("Character code \(currentCharacter) is lowercase.")
  } else if upperCase.characterIsMember(currentCharacter) {
    println("Character code \(currentCharacter) is UPPERCASE.")
  } else {
    println("Character code \(currentCharacter) is neither upper- nor lowercase.")
  }
}

Swift 3

let testString = "Åke röstet un café in Владивосток!"
let lowerCase = CharacterSet.lowercaseLetters
let upperCase = CharacterSet.uppercaseLetters

for currentCharacter in testString.unicodeScalars {
    if lowerCase.contains(currentCharacter) {
        print("Character code \(currentCharacter) is lowercase.")
    } else if upperCase.contains(currentCharacter) {
        print("Character code \(currentCharacter) is UPPERCASE.")
    } else {
        print("Character code \(currentCharacter) is neither upper- nor lowercase.")
    }
}
marc-medley
  • 8,931
  • 5
  • 60
  • 66
guidos
  • 522
  • 6
  • 11
  • @DavidH I'm not so sure, seems a bit inefficient, overall it seems like an O(NM) operation (it performs a search over a set for each element). While the accepted answer seems like (not completely sure) an O(n log n) operation. The important thing to note here is that, the performance of the solution would depend on the size of `CharacterSet.lowercaseLetters` – yeyo Jun 15 '17 at 23:44
  • @yeyo The selected answer does not really answer the question - how to find three types of characters: lower, upper, and a character that has no case. Note that the "contains" operation uses hashes (think set, dictionary) and so is relatively fast. I suspect this is much faster than creating a string as is done in the checked answer, but you'd have to benchmark it yourself. – David H Jun 16 '17 at 13:55
  • Indeed, a benchmark is in order. Using Xcode I can confirm that CharacterSet.lowercaseLetters is not an Array, and to be honest the name itself says it's a SET, which change things I believe. With an else clause, the accepted answer does covert uppercase and lowercase characters, which in fact was my question (initially), however this one, seems be a better solution to the problem, I'll test it and change the accepted answer. thanks – yeyo Jun 16 '17 at 16:21
20

You could always see if the lowercase representation is different from the current value;

let string = "The quick BroWn fOX jumpS Over tHe lazY DOg"
var output = ""

for chr in string {
    var str = String(chr)
    if str.lowercaseString != str {
        output += str
    }
}
print(output)

>>> TBWOXSOHYDO
Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294
  • 6
    This is crazy, all normal languages have a check if char is upper/lower. What about digits and other classes of chars? (sorry, rant) – Andrey Jun 17 '14 at 16:39
  • indeed, I looked and I haven't been able to find such functions in Swift. :\ Maybe I haven't looked enough. – yeyo Jun 17 '14 at 17:41
  • 1
    I think lowercaseString and uppercaseString these two methods are removed from swift – Nitesh Dec 24 '14 at 19:01
  • 1
    @Andrey Swift is implementing a modern `String` type, making localization much easier than it used to be and, at the same time, integrates itself into the existing Objective-C environment with all its historical baggage. If you want to be an anglo-centric dude from the 70's, you totally can use something like: `for cu in s.utf8 { if isdigit(Int32(cu)) == 1 { println("\(cu) is a digit") } }` Unless you live in an ASCII world, I recommend using the `.lowercaseString()` or better yet `.lowercaseStringWithLocale()` method when changing or testing case in text. – guidos Jan 24 '15 at 16:27
  • @Nitesh I think that's an Xcode auto-complete bug. It just happened to me (hence my upvote on your comment), but if you just restart it, it works fine again. – guidos Jan 24 '15 at 16:33
  • @guidos my native language has non-latin script so I can hardly be "anglo-centric dude", I just got used that you can do something like this https://msdn.microsoft.com/en-us/library/9s91f3by(v=vs.110).aspx and I don't see how is it ango-centric. It uses Unicode categories http://en.wikipedia.org/wiki/Unicode_character_property#General_Category – Andrey Jan 25 '15 at 21:17
  • @Andrey I didn't say you __were__ anglo-centric, I said "if you want to be" you could use a testing function from a "normal language" like C's `isupper()`. The point I was trying to make, unsuccessfully, is that it is [not normal for programming languages](http://stackoverflow.com/questions/3325303/is-there-a-programming-language-with-full-and-correct-unicode-support) to get this kind of testing right for Unicode scenarios like you claimed in your original comment. – guidos Jan 26 '15 at 15:15
  • @guidos but it is not about *language*, as I understand C# implements Unicode standards. If there is a thing in Unicode "upper case category" then checking for it is compliant with standard. – Andrey Jan 26 '15 at 16:08
  • @Andrey, if it is not about language, why did you use that word in your original comment? Maybe I'm misunderstanding your original point. Swift __is__ implementing the Unicode standard. It is also compatible to the existing OS X / iOS environments. There are hundreds of classes that each solve different problems. For categorizing characters there is the NSCharacterSet class (see below for example), which was not part of the Objective-C language before and is not part of the Swift language now. Isn't it good to preserve existing infrastructure when writing a drop-in replacement language? – guidos Jan 26 '15 at 22:29
  • Yes, these patterns are crazy, stupid slow because they're O(N^2) or worse because of unnecessary object (con/de)struction. My app right now is taking over 1 minute to parse and sanitize under 1k names, addresses and phone numbers because of this bullshit. –  Jul 31 '15 at 02:32
20

In Swift 5, we can now check for character properties per Unicode standard.

For your question, chr.isUppercase and chr.isLowercase is the answer.

Thanh Pham
  • 2,021
  • 21
  • 30
10

By extending String and Character I think I've arrived at a fairly flexible solution that is fully(?) Unicode aware. The syntax below is for Swift 3.0. Though there is nothing here that should not be possible in Swift 2.x.

extension String {
    func isUppercased(at: Index) -> Bool {
        let range = at..<self.index(after: at)
        return self.rangeOfCharacter(from: .uppercaseLetters, options: [], range: range) != nil
    }
}

extension Character {
    var isUppercase: Bool {
      let str = String(self)
      return str.isUppercased(at: str.startIndex)
    }
}

let str = "AaÀàΓγ!2"
let uppercase = str.characters.filter({ $0.isUppercase }) // ["A", "À", "Γ"]
for char in str.characters {
    "\(char): \(char.isUppercase)"
}

// A: true
// a: false
// À: true
// à: false
// Γ: true
// γ: false
// !: false
// 2: false
// : false
// : false

The TODO for these extensions is to refactor isUppercase on Character to not convert to String.

Ryan
  • 6,432
  • 7
  • 40
  • 54
7

In Swift 3, I do this sort of thing

import UIKit
...
let ch = "A".unicodeScalars.first!
let is_ch_upperCase = CharacterSet.uppercaseLetters.contains(ch)
Ingo Karkat
  • 167,457
  • 16
  • 250
  • 324
Jason
  • 413
  • 5
  • 11
6

SWIFT 4:

You can simply just do no need for all the other fancy stuff unless you are checking for numerals or unicode scalars, etc.:

extension String {

   var isLowercase: Bool {
       return self == self.lowercased()
   }

   var isUppercase: Bool {
       return self == self.uppercased()
   }

}
AndyRoid
  • 5,062
  • 8
  • 38
  • 73
5

Cleaner, but possibly slower than looping:

let str = "The quick BroWn fOX jumpS Over tHe lazY DOg"
let nonUpperCase = NSCharacterSet.uppercaseLetterCharacterSet().invertedSet
let letters = str.componentsSeparatedByCharactersInSet(nonUpperCase)
"".join(letters) // "TBWOXSOHYDO"
Matt Wilding
  • 20,115
  • 3
  • 67
  • 95
4

Expanding on Bob Prystaneks function, you could always extend the Swift "Character" class. Just put the following extension anywhere in your code, and now you can ask any Character if it's uppercase.

extension Character
{
    public func isUpper() -> Bool
    {
        let characterString = String(self)
        return (characterString == characterString.uppercaseString) && (characterString != characterString.lowercaseString)
    }
}
3

Simple extension accepts any CharacterSet to check:

extension String {
    func hasCharacter(in characterSet: CharacterSet) -> Bool {
        return rangeOfCharacter(from: characterSet) != nil
    }
}

usage:

"aBc".hasCharacter(in: .lowercaseLetters)
"aBc".hasCharacter(in: .uppercaseLetters)
"aBc".hasCharacter(in: <#AnyOtherCharacterSetYouWant#>)
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278
2

Add this Swift extension so that all instances of Character class have two new functions: isUpperCase() to test if the character is upper case and isLowerCase() to test if the character is lower case. The implementation simply checks to see if the character instance is in the required set.

extension Character {

        func isUpperCase() -> Bool {
            return CharacterSet.uppercaseLetters.contains(self.unicodeScalars.first!)
        }

        func isLowerCase() -> Bool {
            return CharacterSet.lowercaseLetters.contains(self.unicodeScalars.first!)
        }

  }
R OMS
  • 652
  • 2
  • 7
  • 19
  • Your answer contains only code. It would be better if you could also add some commentary to explain what it does and how. Can you please [edit] your answer and add it? Thank you! – Fabio says Reinstate Monica Oct 12 '17 at 01:08
2

Swift 5 Check with character properties

let string = "The quick BroWn fOX jumpS Over tHe lazY DOg"
for (_,ch) in string.enumerated() {
    if ch.isUppercase {
         //uppercase
   } else {
        //lowercase: ch.isLowercase
   }
}

Swift 4 Enumerate a string and get ascii code for character and then compare.

ascii code for

a -> 97 b -> 98 ...

A -> 65 B -> 66 ...

let string = "The quick BroWn fOX jumpS Over tHe lazY DOg"
for (_,ch) in string.enumerated() {
    let chValue = String(ch).unicodeScalars
    let asciiValue = chValue[chValue.startIndex].value
    if asciiValue >= 97 {
           //lowercase
    } else {
           //uppercase
    }
}
Mohit Kumar
  • 2,898
  • 3
  • 21
  • 34
1

As other has commented, using only uppercaseString or lowercaseString doesn't take "non-alpha" characters into account.

This is how I implemented isupper. A character is only an uppercase if it isn't equal to its lowercase.

func isupper(c:Character) -> Bool
{
    let cs = String(c)
    return (cs == cs.uppercaseString) && (cs != cs.lowercaseString)
}
Bob Prystanek
  • 431
  • 3
  • 5
1

create and use this extension:

extension Character {
    var isUppercase: Bool {
        guard self.asciiValue != nil else {
            return false
        }

        return self.asciiValue! >= Character("A").asciiValue! &&
                self.asciiValue! <= Character("Z").asciiValue!
    }

    var asciiValue: UInt32? {
        return String(self).unicodeScalars.first?.value
    }
}
cypher
  • 101
  • 1
  • 3
0

Every character can be presented a UTF or Unicode value (8/16/21 Bit):

For example:

UTF-8 "Dog!": 68, 111, 103, 33
Unicode (Dog Face): U+1F436

So if you wanna check for upper and lower case, you can just match the UTF range.

(PS: Watch out for different UTF-XX/Unicode formats)

Javatar
  • 2,518
  • 1
  • 31
  • 43
  • UTF ranges of all possible languages? Also good look with ranges and chars that are wider than 1 byte in UTF-8 encoding. – Andrey Jun 17 '14 at 16:45
  • What do you mean? All character can be represented by 32(21)Bit Unicode if you need more! http://unicode-table.com/de/#control-character – Javatar Jun 18 '14 at 08:30
  • I mean that your recommendation is not really helpful. First of all why did you even mention UTF? You didn't even specify what UTF, 8 or 16 or 32. (UTF "Dog!" is UTF-8). Second problem - checking ranges is not really an option because there are lots of languages/scripts, beside English/Latin. – Andrey Jun 18 '14 at 08:51
  • It's another attempt. U can specify your 'own'(needed) range. Chinese and other langauages don't even have case sentive letters (words). But I agree maybe this is too unwieldy for this case. – Javatar Jun 18 '14 at 12:48
0

create and use this extension:

extension Character {
        var isUppercase: Bool {
            return (String(self).unicodeScalars.filter {
                $0.value >= "A".unicodeScalars.first!.value &&
                $0.value <= "Z".unicodeScalars.first!.value
            }).count != 0
        }
    }
cypher
  • 101
  • 1
  • 3
0

Swift 3:

var str = "Hello"

let upperChars = str.unicodeScalars.flatMap({ char in
    CharacterSet.uppercaseLetters.contains(char) ? char : nil
})

let lowerChars = str.unicodeScalars.flatMap({ char in
    CharacterSet.lowercaseLetters.contains(char) ? char : nil
})

// upperChars -> ["H"]
// lowerChars -> ["e", "l", "l", "o"]
nicksweet
  • 3,929
  • 1
  • 20
  • 22
0

Simple Swifty extension :

extension String {
    var isLower: Bool {
        return String(self.filter { String($0) == String($0).lowercased() }) == self
    }
}
Musa almatri
  • 5,596
  • 2
  • 34
  • 33
0

Just to check if string contains Upper, Lower or Special characters:

let lowerCase = CharacterSet.lowercaseLetters
let upperCase = CharacterSet.uppercaseLetters
let numbers = CharacterSet.decimalDigits

let containsNumbers = text.unicodeScalars.contains(where: { numbers.contains($0) })
let containsLowerCase = text.unicodeScalars.contains(where: { lowerCase.contains($0) })
let containsUpperCase = text.unicodeScalars.contains(where: { upperCase.contains($0) })
let containsSpecial = text.unicodeScalars.contains(where: { !lowerCase.contains($0) && !upperCase.contains($0) && !numbers.contains($0) })
HelloimDarius
  • 695
  • 5
  • 23