My input is like "Hi {{username}}"
, ie. a string with keywords to replace. However, the input is quite small (~ 10 keywords and 1000 characters total), and I have a million possible keywords stored in a hashtable data structure, each associated to its replacement.
Therefore, I do not want to iterate over the keyword list and try to replace each one in the input for obvious performance reason. I prefer to iterate only once over the input characters by looking for the regex pattern "\{\{.+?\}\}"
.
In Java, I make use of the Matcher.appendReplacement
and Matcher.appendTail
methods to do that. But I cannot find a similar API with NSRegularExpression
.
private String replaceKeywords(String input)
{
Matcher m = Pattern.compile("\\{\\{(.+?)\\}\\}").matcher(input);
StringBuffer sb = new StringBuffer();
while (m.find())
{
String replacement = getReplacement(m.group(1));
m.appendReplacement(sb, replacement);
}
m.appendTail(sb);
return sb.toString();
}
Am I forced to implement such API myself, or did I miss something?
You can achieve this with NSRegularExpression
:
NSString *original = @"Hi {{username}} ... {{foo}}";
NSDictionary *replacementDict = @{@"username": @"Peter", @"foo": @"bar"};
NSString *pattern = @"\\{\\{(.+?)\\}\\}";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
NSMutableString *replaced = [original mutableCopy];
__block NSInteger offset = 0;
[regex enumerateMatchesInString:original
options:0
range:NSMakeRange(0, original.length)
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange range1 = [result rangeAtIndex:1]; // range of the matched subgroup
NSString *key = [original substringWithRange:range1];
NSString *value = replacementDict[key];
if (value != nil) {
NSRange range = [result range]; // range of the matched pattern
// Update location according to previous modifications:
range.location += offset;
[replaced replaceCharactersInRange:range withString:value];
offset += value.length - range.length; // Update offset
}
}];
NSLog(@"%@", replaced);
// Output: Hi Peter ... bar
A Swift version using the Regex
type that was introduced with Swift 5.7, requires macOS 13+ or iOS 16+):
import Foundation
let original = "Hi {{username}} ... {{foo}}"
let replacementDict = ["username" : "Peter", "foo" : "bar" ]
var replaced = ""
let regex = /\{\{(.+?)\}\}/
var pos = original.startIndex
// Look for next occurrence of {{key}}.
while let match = try? regex.firstMatch(in: original[pos...]) {
// Append original text preceding the match.
replaced += original[pos..<match.range.lowerBound]
// Replace {{key}} by corresponding value and append to the output.
let key = String(match.1)
if let value = replacementDict[key] {
replaced += value
} else {
replaced += match.0
}
// Continue search after the current match.
pos = match.range.upperBound
}
// Append original text after the final match.
replaced += original[pos...]
print(replaced)
// Hi Peter ... bar