Tiago Tiago - 1 month ago 6
Objective-C Question

Uppercase random characters in a NSString

I'm trying to figure out the best approach to a problem. I have an essentially random alphanumeric string that I'm generating on the fly:

NSString *string = @"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";

You can see that there are no special characters, just numbers and letters, and all the letters are lowercase. Changing all the letters in this string to uppercase is easy:

[string capitalizedString];

The hard part is that I want to capitalize random characters in this string, not all of them. For example, this could be the output on one execution:


This could be the output on another, since it's random:


In case it makes this easier, let's say I have two variables as well:

int charsToUppercase = 12;//hardcoded value for how many characters to uppercase here
int totalChars = 90;//total string length

In this instance it would mean that 12 random characters out of the 90 in this string would be uppercased. What I've figured out so far is that I can loop through each char in the string relatively easily:

NSUInteger len = [string length];
unichar buffer[len+1];

[string getCharacters:buffer range:NSMakeRange(0, len)];

NSLog(@"loop through each char");
for(int i = 0; i < len; i++) {
NSLog(@"%C", buffer[i]);

Still stuck with selecting random chars in this loop to uppercase, so not all are uppercased. I'm guessing a condition in the for loop could do the trick well, given that it's random enough.


Here's one way, not particularly concerned with efficiency, but not silly efficiency-wise either: create an array characters in the original string, building an index of which ones are letters along the way...

NSString *string = @"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
NSMutableArray *chars = [@[] mutableCopy];
NSMutableArray *letterIndexes = [@[] mutableCopy];

for (int i=0; i<string.length; i++) {
    unichar ch = [string characterAtIndex:i];
    // add each char as a string to a chars collection
    [chars addObject:[NSString stringWithFormat:@"%c", ch]];
    // record the index of letters
    if ([[NSCharacterSet letterCharacterSet] characterIsMember:ch]) {
        [letterIndexes addObject:@(i)];

Now, select randomly from the letterIndexes (removing them as we go) to determine which letters shall be upper case. Convert the member of the chars array at that index to uppercase...

int charsToUppercase = 12;

for (int i=0; i<charsToUppercase && letterIndexes.count; i++) {
    NSInteger randomLetterIndex = arc4random_uniform((u_int32_t)(letterIndexes.count));
    NSInteger indexToUpdate = [letterIndexes[randomLetterIndex] intValue];
    [letterIndexes removeObjectAtIndex:randomLetterIndex];

    [chars replaceObjectAtIndex:indexToUpdate withObject:[chars[indexToUpdate] uppercaseString]];

Notice the && check on letterIndexes.count. This guards against the condition where charsToUppercase exceeds the number of chars. The upper bound of conversions to uppercase is all of the letters in the original string.

Now all that's left is to join the chars array into a string...

NSString *result = [chars componentsJoinedByString:@""];
NSLog(@"%@", result);

EDIT Looking discussion in OP comments, you could, instead of acharsToUppercase input parameter, be given a probability of uppercase change as an input. That would compress this idea into a single loop with a little less data transformation...

NSString *string = @"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";

float upperCaseProbability = 0.5;
NSMutableString *result = [@"" mutableCopy];

for (int i=0; i<string.length; i++) {
    NSString *chString = [string substringWithRange:NSMakeRange(i, 1)];
    BOOL toUppercase = arc4random_uniform(1000) / 1000.0 < upperCaseProbability;

    if (toUppercase) {
        chString = [chString uppercaseString];
    [result appendString:chString];
NSLog(@"%@", result);

However this assumes a given uppercase probability for any character, not any letter, so it won't result in a predetermined number of letters changing case.