【问题标题】:Checking NSString for balanced delimiters检查 NSString 的平衡分隔符
【发布时间】:2013-12-16 18:48:39
【问题描述】:

我的输入 (NSString) 可以是 ("text")、{("text")} 或 (“text”){{“text”}}。在所有这些情况下,我必须确保开始分隔符 ({) 有自己的结束分隔符 (})。

例如,{{“text”}) 应标记为错误。

我正在尝试NSScanner 来完成此操作,还尝试反转字符串并比较每个字符以寻找相反的字符,但遇到了一些麻烦。

最好的方法是什么?

这是我尝试过的最新方法:

NSMutableString *reversedString = [NSMutableString string];
NSInteger charIndex = [_expressionTextField.text length];
while (charIndex > 0) {
    charIndex--;
    NSRange subStrRange = NSMakeRange(charIndex, 1);
    [reversedString appendString:[_expressionTextField.text substringWithRange:subStrRange]];
}

NSString *mystring = _expressionTextField.text;

NSLog(@"%@", reversedString);
for (int i = 0; i < reversedString.length; i++) {
    if ([mystring characterAtIndex:i] == [reversedString characterAtIndex:(reversedString.length -i)]) {
        NSLog(@"Closed the bracket");
    }
}

【问题讨论】:

  • 除了简单计数之外,您是否要确保嵌套对按照打开顺序关闭?这样{("text)"} 就无效了?
  • @JoshCaswell — 检查我的编辑。
  • 我真的不明白你用反向字符串的想法去哪里。您需要确保 ( 伴随着 ) - 它们是两个不同的字符,并且不会比较相等 - 不仅仅是存在“括号”。我认为NSScanner 是正确的方法,(我现在没有时间摆弄它)。你也可以看看ParseKit
  • 大致来说,遍历字符串,根据您遇到的开启者保留您希望看到的结束字符的列表(堆栈)。如果您有一个故障,则失败,否则成功。

标签: objective-c string cocoa parsing nsstring


【解决方案1】:

我用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);
}

【讨论】:

  • 感谢您发布此代码。它非常有用。但是,我发现的一个问题是,如果您有一个字符串,例如“这是今天的午餐”,它应该平衡,因为文本周围的双引号完成了字符串,即使里面有一个单引号 "今天的”。但即使字符串中有一个双引号,“这是今天的午餐”也应该验证。两边的单引号都可以。知道如何处理吗?
  • 这听起来很难以一般的方式。如果您想在找到开头的" 后忽略任何 其他分隔符,那么您可以scanUpToString:intoString: 直接跳到结束分隔符或字符串的末尾。另一种选择是对有效收缩进行特殊处理。即,如果您在' 之后找到s,则将' 从分隔符堆栈中弹出并继续。
【解决方案2】:

您必须跟踪最新的分隔符和出现的情况。

您可以为此使用一个堆栈:通过每个找到的开始分隔符。当你找到正确的结束时,删除最后一个。

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSMutableArray *stack = [NSMutableArray array];
        NSString *text = @"(“text”){{“text”}}}";
        NSArray *delimiterPairs = @[@[@"(", @")"],@[@"{", @"}"]];

        NSMutableString *openingDelimiters = [@"" mutableCopy];
        NSMutableString *closingDelimiters = [@"" mutableCopy];
        [delimiterPairs enumerateObjectsUsingBlock:^(NSArray *pair, NSUInteger idx, BOOL *stop) {
            [openingDelimiters appendString:pair[0]];
            [closingDelimiters appendString:pair[1]];
        }];

        NSScanner *scanner = [NSScanner scannerWithString:text];
        __block BOOL unbalanced = NO;
        while (![scanner isAtEnd] && !unbalanced) {
            [scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:[openingDelimiters stringByAppendingString:closingDelimiters]]
                                    intoString:NULL];
            NSString *currentDelimiter = [text substringWithRange:NSMakeRange([scanner scanLocation], 1)];
            if ([openingDelimiters rangeOfString:currentDelimiter].location != NSNotFound) {
                [stack addObject:currentDelimiter];
            } else {
                [delimiterPairs enumerateObjectsUsingBlock:^(NSArray *pair, NSUInteger idx, BOOL *stop) {
                    if([currentDelimiter isEqualToString:pair[1]]){
                        if([stack count] == 0) {
                            unbalanced = YES;
                        } else if ([[stack lastObject] isEqualToString:pair[0]]) {
                            [stack removeLastObject];
                        }
                        *stop = YES;
                    }
                }];
            }
            scanner.scanLocation +=1;
        }
        if ([stack count])
            unbalanced = YES;            
    }
    return 0;
}

如果分隔符不匹配,则布尔不平衡为YES;

【讨论】:

    猜你喜欢
    • 2018-01-05
    • 2017-03-07
    • 2013-02-03
    • 2015-12-16
    • 1970-01-01
    • 2013-02-02
    • 2013-02-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多