【问题标题】:Using NSPredicate to analyze strings使用 NSPredicate 分析字符串
【发布时间】:2011-05-31 21:31:51
【问题描述】:

你好!这个问题很长,所以一定要先坐下:)

在我的代码中的某一时刻,我从用户那里得到了一个字符串,然后我分析了那个字符串。通过分析,我的意思是遍历每个字符并将其与用户在 NSPredicateEditor 中设置的谓词进行数学运算。谓词是这样设置的:

NSArray* keyPaths = [NSArray arrayWithObjects:
                         [NSExpression expressionForKeyPath: @"Character"],
                         [NSExpression expressionForKeyPath: @"Character Before"],
                         [NSExpression expressionForKeyPath: @"Character After"],
                         nil];
// -----------------------------------------
NSArray* constants = [NSArray arrayWithObjects:
                      [NSExpression expressionForConstantValue: @"Any letter"],
                      [NSExpression expressionForConstantValue: @"Letter (uppercase)"],
                      [NSExpression expressionForConstantValue: @"Letter (lowercase)"],
                      [NSExpression expressionForConstantValue: @"Number"],
                      [NSExpression expressionForConstantValue: @"Alphanumerical"],
                      [NSExpression expressionForConstantValue: @"Ponctuation Mark"],
                      nil];
// -----------------------------------------
NSArray* compoundTypes = [NSArray arrayWithObjects:
                          [NSNumber numberWithInteger: NSNotPredicateType],
                          [NSNumber numberWithInteger: NSAndPredicateType],
                          [NSNumber numberWithInteger: NSOrPredicateType],
                          nil];
// -----------------------------------------
NSArray* operatorsA = [NSArray arrayWithObjects:
                       [NSNumber numberWithInteger: NSEqualToPredicateOperatorType],
                       [NSNumber numberWithInteger: NSNotEqualToPredicateOperatorType],
                       nil];

NSArray* operatorsB = [NSArray arrayWithObjects:
                       [NSNumber numberWithInteger: NSInPredicateOperatorType],
                       nil];
// -----------------------------------------
NSPredicateEditorRowTemplate* template1 = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions: keyPaths
                                                                                       rightExpressions: constants
                                                                                               modifier: NSDirectPredicateModifier 
                                                                                              operators: operatorsA
                                                                                                options: 0];

NSPredicateEditorRowTemplate* template2 = [[NSPredicateEditorRowTemplate alloc] initWithLeftExpressions: keyPaths
                                                                           rightExpressionAttributeType: NSStringAttributeType
                                                                                               modifier: NSDirectPredicateModifier 
                                                                                              operators: operatorsB
                                                                                                options: 0];

NSPredicateEditorRowTemplate* compound = [[NSPredicateEditorRowTemplate alloc] initWithCompoundTypes: compoundTypes];
// -----------------------------------------
NSArray* rowTemplates = [NSArray arrayWithObjects: template1, template2, compound, nil];

[myPredicateEditor setRowTemplates: rowTemplates];

所以你可以看到我有三个键路径和一些可以与之比较的常量。 在分析字符串时,我基本上想这样做,在伪代码中:

originalString = [string from NSTextView]
for (char in originalString)
    bChar = [character before char]
    aChar = [character after char]

    predicate = [predicate from myPredicateEditor]

    // using predicate - problem!
    result = [evaluate predicate with:
               bChar somehow 'linked' to keypath 'Character Before'
               char 'linked' to 'Character'
               aChar 'linked' to 'Character After' // These values change, of course
              and also:
               constant "All letters" as "abc...WXYZ"
               constant "Numbers" as "0123456789"
               etc for all other constants set up  // These don't
              ]

    if (result) then do something with char, bChar and aChar

你可以看到我的问题基本上出在哪里:

  • 'Character Before/After' 由于空间的原因不能是键路径,但我想保持这种方式,因为它对用户来说更漂亮(想象一下有一些东西作为'characterBefore'......)

  • 诸如“数字”之类的常量实际上代表了诸如“0123456789”之类的字符串,我也无法向用户显示

我能够找到解决此问题的方法,但我现在它不适用于每个角色,而且效率也很低(至少在我看来)。我所做的是从谓词中获取谓词格式,替换我必须替换的内容,然后评估新格式。现在来看一些解释这一点的真实代码:

#define kPredicateSubstitutionsDict [NSDictionary dictionaryWithObjectsAndKeys: \
@"IN 'abcdefghijklmnopqrstuvwxyz'", @"== \"Letter (lowercase)\"", \
@"IN 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'", @"== \"Letter (uppercase)\"", \
@"IN 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'", @"== \"Any letter\"", \
@"IN '1234567890'", @"== \"Number\"", \
@"IN 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'", @"== \"Alphanumerical\"", \
@"IN ',.;:!?'", @"== \"Ponctuation Mark\"", \
\
@"MATCHES '[^a-z]'"         , @"!= \"Letter (lowercase)\"", \
@"MATCHES '[^A-Z]'"         , @"!= \"Letter (uppercase)\"", \
@"MATCHES '[^a-zA-Z]'"      , @"!= \"Any letter\"", \
@"MATCHES '[^0-9]'"         , @"!= \"Number\"", \
@"MATCHES '[^a-zA-Z0-9]'"   , @"!= \"Alphanumerical\"", \
@"MATCHES '[^,\.;:!\?]'"  , @"!= \"Ponctuation Mark\"", \
\
nil]

// NSPredicate* predicate is setup in another place
// NSString* originalString is also setup in another place
NSString* predFormat = [predicate predicateFormat];
for (NSString* key in [kPredicateSubstitutionsDict allKeys]) {
    prefFormat = [predFormat stringByReplacingOccurrencesOfString: key withString: [kPredicateSubstitutionsDict objectForKey: key]];
}

for (NSInteger i = 0; i < [originalString length]; i++) {
    NSString* charString = [originalString substringWithRange: NSMakeRange(i, 1)];
    NSString* bfString;
    NSString* afString;

    if (i == 0) {
            bfString = @"";
    }
    else {
            bfString = [originalString substringWithRange: NSMakeRange(i - 1, 1)];
    }

    if (i == [originalString length] - 1) {
            afString = @"";
    }
    else {
            afString = [originalString substringWithRange: NSMakeRange(i + 1, 1)];
    }

    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character Before" withString: [NSString stringWithFormat: @"\"%@\"", bfString]];
    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character After" withString: [NSString stringWithFormat: @"\"%@\"", afString]];
    predFormat = [predFormat stringByReplacingOccurrencesOfString: @"Character" withString: [NSString stringWithFormat: @"\"%@\"", charString]];

    NSPredicate* newPred = [NSPredicate predicateWithFormat: predFormat];
    if ([newPred evaluateWithObject: self]) { // self just so I give it something (nothing is actually gotten from self)
        // if predicate evaluates to true, then do something exciting!
    }
}

所以,给你,这是我正在做的简化版本。如果您看到任何拼写错误,很可能它们不在我的代码中,因为我已经对此进行了相当多的编辑,所以它会更简单。

总结一下:

  • 我需要评估用户针对许多字符所做的谓词,对其进行大量修改,同时尽可能提高效率
  • 我发现我的方法存在的问题是:

    • 我觉得它一点也不干净
    • 我不保证它适用于所有情况(当其中一个字符是换行符/回车符时,谓词会引发错误,说它无法理解格式)

这就是所有的人!感谢您到目前为止的阅读。解决这个烂摊子时,愿你的上帝与你同在!

编辑:只是为了澄清一些事情,我要补充一点,这个问题的触发因素是我不能在设置谓词编辑器的一开始就定义一个带有名称的常量(显示给用户)和一个表示该常量并以谓词格式插入的值。键路径也是如此:如果我可以有一个显示名称,然后一个值就是谓词的那些 var 字符串($VAR 或其他任何东西),那么所有问题都将得到解决。如果这是可能的,请告诉我如何。如果不可能,那么请关注我描述的其他问题。

【问题讨论】:

    标签: objective-c string nspredicate nspredicateeditor


    【解决方案1】:

    所以主要问题:

    您能否构建一个NSPredicateEditorRowTemplate,它使用与用户界面中显示的不同的常量?

    回答:是的。您以前可以在 Interface Builder 中进行设置,但我在 Xcode 4 中的尝试没有成功。可能在 IB => Xcode 4 合并中删除了该功能。

    幸运的是,您可以在代码中做到这一点。不幸的是,它需要一个NSPredicateEditorRowTemplate 子类。如果没有这样的子类,我还没有找到一种方法。

    要点是在您的行模板中覆盖-templateViews,并执行以下操作:

    - (NSArray *)templateViews {
      NSArray *views = [super templateViews];
      // views[0] is the left-hand popup
      // views[1] is the operator popup
      // views[2] is the right-hand side (textfield, popup, whatever)
      NSPopUpButton *left = [views objectAtIndex:0];
      NSArray *items = [left itemArray];
      for (NSMenuItem *item in items) {
        //the representedObject of the item is the expression that will be used in the predicate
        NSExpression *keyPathExpression = [item representedObject];
    
        //we can alter the -title of the menuItem without altering the expression object
        if ([[keyPathExpression keyPath] isEqual:@"before"]) {
          [item setTitle:@"Character Before"];
        } else if ([[keyPathExpression keyPath] isEqual:@"after"]) {
          [item setTitle:@"Character After"];
        }
      }
      return views;
    }
    

    瞧!谓词编辑器中的一个弹出窗口,其中标题与键路径不匹配。

    编辑

    解决此问题的另一种方法(无需子类化!)是将您的自定义文本放入 .strings 文件中,并将您的编辑器“本地化”为英语(或您想要的任何语言)。


    想象一下这种情况:用户在谓词编辑器中输入这个表达式 Character/is not/Number。该谓词的格式将是“字符!=”0123456789“这总是正确的,即使字符是数字!我如何“替换”这些运算符:is/is not 与它们的实际函数 IN/NOT (IN . ..)?

    如果您要在谓词中表达这些比较,您可能不会使用!===。您可能想要执行以下操作:

    character MATCHES '[0-9]'
    

    其中之一:

    character MATCHES '[^0-9]'
    NOT(character MATCHES '[0-9]')
    

    为此,我能想到的方法是拥有两个单独的行模板。一个会识别并显示第一种格式的谓词(MATCHES '[0-9]'),第二个会识别并显示否定。我想你开始进入NSPredicateEditorRowTemplates 的奇怪领域。我仍然不确定您要完成什么以及为什么要将字符串的每个字符与谓词匹配,但无论如何。

    有关如何创建自定义行模板的更多信息,请查看以下两篇博文:

    【讨论】:

    • 这很聪明,解决了这个问题!工作起来就像一个魅力,但是,现在我想到了,还有另一件事要解决,那就是比较运算符(或任何它们被称为的东西)。想象一下这种情况:用户在谓词编辑器中放入这个表达式 Character/is not/Number。该谓词的格式将是“字符!=”0123456789“这总是正确的,即使字符是数字!我如何“替换”这些运算符:is/is not 与它们的实际函数 IN/NOT (IN . ..)?谢谢!
    【解决方案2】:

    为什么设置可视文本需要子类?简单地在你的类中创建一个方法会不会更便宜:

        - (void)setVisibleMenuTitlesInPredicate:(NSPredicateEditorRowTemplate *)predicateTemplate
        {
            NSArray *predicateTemplateViews = [predicateTemplate templateViews];
            NSPopUpButton *leftMenu = [predicateTemplateViews objectAtIndex:0];
            NSArray *leftMenuList = [leftMenu itemArray];
            for (NSMenuItem *menuItem in leftMenuList) {
                id keyPathValue = [menuItem representedObject];
                if ([[keyPathExpression keyPath] isEqual:@"before"]) {
                      [menuItem setTitle:@"Character Before"];
                    } else if ([[keyPathExpression keyPath] isEqual:@"after"]) {
                      [menuItem setTitle:@"Character After"];
                    }
        }
    

    然后在 setRowTemplates 之前调用它:像这样:

        [self setVisibleMenuTitlesInPredicate:predicateTemplateA];
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-27
      • 1970-01-01
      • 2014-09-14
      • 1970-01-01
      相关资源
      最近更新 更多