Search code examples
objective-cregexnsstringnsrange

How to replace multiple regular expression matches in NSString?


I would like to know how is it possible to replace specific characters of an NSString based on some regular expression ([A-G]#?). More analytically, I have a NSString full of chords and spaces
e.g. " C A A# B".

What I want is to change these chords based on a dictionary that I made:
@"A", @"A#",
@"A#", @"B",
@"B", @"C",
@"C", @"C#",
@"C#", @"D",
@"D", @"D#",
@"D#", @"E",
@"E", @"F",
@"F", @"F#",
@"F#", @"G",
@"G", @"G#",
@"G#", @"A",

meaning that
A -> A# and A#-> B.
I would also like to keep anything that exists after my basic chord, meaning:
Am -> A#m.

I have already successfully scanned the string and replaced the elements, with the ones I wanted, in a new string, however I can't figure out how can I do that while maintaining the spaces of my initial string.

So to sum things, basically I want this:
" C A A# B"
to become this:
" C# A# B C"

Thank you!


Solution

  • The following code replaces all chords from the dictionary while preserving everything else (so that "Am" is replaced by "A#m" as requested):

    NSDictionary *transposeDict = @{
        @"A": @"A#", @"A#": @"B", @"B": @"C", @"C": @"C#", @"C#": @"D",
        @"D": @"D#", @"D#": @"E", @"E": @"F", @"F": @"F#", @"F#": @"G",
        @"G": @"G#", @"G#": @"A"
    };
    
    NSString *melody = @" C A A# B   Am";
    NSMutableString *transposedMelody = [melody mutableCopy];
    
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"([A-G]#?)"
                                                                           options:0
                                                                             error:NULL];
    
    NSArray *matches = [regex matchesInString:melody options:0 range:NSMakeRange(0, [melody length])];
    
    for (NSTextCheckingResult *match in [matches reverseObjectEnumerator]) {
        NSString *oldChord = [melody substringWithRange:match.range];
        NSString *newChord = transposeDict[oldChord];
        if (newChord != nil)
            [transposedMelody replaceCharactersInRange:match.range withString:newChord];
    }
    
    
    NSLog(@"old: %@", melody);
    NSLog(@"new: %@", transposedMelody);
    

    Output:

    old:  C A A# B   Am
    new:  C# A# B C   A#m
    

    The array matches contains all ranges of matching substrings. These ranges are then processed in reverse order (from last match to first match), so that mutating the string (which might change the length) does not influence the locations of the remaining ranges.