我用NSScanner 破解了它。对于很长的字符串,我认为这会比 vikingosegundo 快一点,因为我一次只是直接穿过一个角色。没有搜索或子串制作。对于大多数目的,它可能不会有什么不同。
/// Takes a string and a dictionary of delimiter pairs in which the keys are the
/// opening characters of the pairs and the values the closers. Returns YES if the
/// delimiters in the string are balanced, otherwise NO. Ignores any characters
/// not present in the dictionary.
///
/// Note: Does not support multi-character delimiters.
BOOL stringHasBalancedDelimiters(NSString * s, NSDictionary * delimiterPairs)
{
NSMutableArray * delimiterStack = [NSMutableArray array];
NSString * openers = [[delimiterPairs allKeys] componentsJoinedByString:@""];
NSString * closers = [[delimiterPairs allValues] componentsJoinedByString:@""];
NSCharacterSet * openerSet = [NSCharacterSet characterSetWithCharactersInString:openers];
NSCharacterSet * closerSet = [NSCharacterSet characterSetWithCharactersInString:closers];
NSMutableCharacterSet * delimiterSet = [openerSet mutableCopy];
[delimiterSet formUnionWithCharacterSet:closerSet];
NSScanner * scanner = [NSScanner scannerWithString:s];
while( ![scanner isAtEnd] ){
// Move up to the next delimiter of either kind
[scanner scanUpToCharactersFromSet:delimiterSet intoString:nil];
NSString * delimiter;
// Could be a closer.
if( [scanner WSSScanSingleCharacterFromSet:closerSet intoString:&delimiter] ){
// Got a paired closer; pop the opener off the stack and continue.
NSString * expected = [delimiterStack lastObject];
if( [expected isEqualToString:delimiter] ){
[delimiterStack removeLastObject];
continue;
}
// Not the right closer, but if the members of the pair are
// identical, treat as an opener.
else if( [delimiterPairs[delimiter] isEqualToString:delimiter] ){
[delimiterStack addObject:delimiterPairs[delimiter]];
continue;
}
// Otherwise this is a failure.
else {
return NO;
}
}
// Otherwise it's an opener (or nothing, thus the if).
if( [scanner WSSScanSingleCharacterFromSet:openerSet intoString:&delimiter] ){
[delimiterStack addObject:delimiterPairs[delimiter]];
}
}
// Haven't failed and nothing left to pair? Success.
return [delimiterStack count] == 0;
}
我向NSScanner 添加了一个方法,让我的生活更轻松。这样我们就不必扫描一堆字符(因为分隔符可以彼此相邻)然后将它们分成单独的NSStrings。
@interface NSScanner (WSSSingleCharacter)
- (BOOL)WSSScanSingleCharacterFromSet:(NSCharacterSet *)charSet intoString:(NSString * __autoreleasing *)string;
@end
@implementation NSScanner (WSSSingleCharacter)
- (BOOL)WSSScanSingleCharacterFromSet:(NSCharacterSet *)charSet intoString:(NSString *__autoreleasing *)string
{
if( [self isAtEnd] ) return NO;
NSUInteger loc = [self scanLocation];
unichar character = [[self string] characterAtIndex:loc];
if( [charSet characterIsMember:character] ){
if( string ){
*string = [NSString stringWithCharacters:&character length:1];
}
[self setScanLocation:loc+1];
return YES;
}
else {
return NO;
}
}
@end
一些测试:
NSDictionary * delimiterPairs = @{@"{" : @"}",
@"[" : @"]",
@"\"" : @"\"",
@"'" : @"'",
@"(" : @")"};
// Balanced simple nesting
NSString * s = @"{(\"text\")}";
// Balanced complex nesting
NSString * t = @"{({}'(text)[\"\"]')text}";
// Balanced symmetrical delimiters at beginning and end of string, as
// well as after both an opener and closer from a different pair
NSString * u = @"\"\"(\"text\"\"\")\"\"";
// Out of order
NSString * v = @"{(\"text)\"}";
// Unpaired at the beginning
NSString * w = @"\"{text}";
// Unpaired at the end
NSString * x = @"\"'text'\"(";
// Unpaired in the middle
NSString * y = @"[(text)']";
for( NSString * string in @[s, t, u, v, w, x, y] ){
BOOL paired = stringHasBalancedDelimiters(string, delimiterPairs);
NSLog(@"%d", paired);
}