使用 NSPredicate 解析带有变量的公式

Posted

技术标签:

【中文标题】使用 NSPredicate 解析带有变量的公式【英文标题】:Use NSPredicate to parse a formula with variables 【发布时间】:2012-02-22 18:37:11 【问题描述】:

任务:

我打算在 NSPredicate 中解析一个公式字符串,并用它们的数值替换字符串中的变量。这些变量是我的数据模型中现有对象实例的属性名称,例如,我有一个类“company”和一个实例“Apple Corp”。

设置:

我的公式应该如下所示:“Profitability_2011_in% = [Profit 2011] / [Revenue 2011]”

“Apple Corp”实例将具有以下属性:

2009 年收入 = 10,2010 年收入 = 20,2011 年收入 = 30, 2009 年利润 = 5,2010 年利润 = 10,2011 年利润 = 20。

因此,该公式将得出 20 / 30 = 67%。

变量通常是二维的,例如将“利润”定义为财务报表项目和“年份”(例如 2011 年)。 变量用 [ ] 括起来,尺寸用“”(空格)分隔。

我会怎么做

我的实现将从 NSRegularExpression 的matchesInString:options:range: 开始,以获取公式中所有变量的数组(Profit 2011,Revenue 2011),然后通过查询我的数据模型从该数组中构造一个 NSDictionary(键 = 变量名)。

你怎么看?

您认为有更好的方法吗? 在公式中,如何将变量替换为它们的值? 您将如何解析公式?

谢谢!!

【问题讨论】:

【参考方案1】:

是的,您可以这样做。这属于“将NSPredicate 用于非预期用途”的类别,但可以正常工作。

您需要用一个以$ 开头的单词替换变量,因为NSPredicate 就是这样表示变量的:

NSPredicate *p = [NSPredicate predicateWithFormat:@"foo = $bar"];

不管你想这样做,太好了。 NSRegularExpression 是一个很好的方法。

一旦你这样做了,你就会有这样的东西:

@"$profitability2011 = $profit2011 / $revenue2011"

然后您可以通过+predicateWithFormat: 弹出此消息。您将收到NSComparisonPredicate-leftExpression 的类型为 NSVariableExpressionType-rightExpression 的类型为 NSFunctionExpressionType

这就是事情开始变得棘手的地方。如果您要使用-evaluteWithObject:substitutionVariables:,您只需返回YESNO 值,因为predicate is simply a statement that evaluates to true or false。我还没有探讨过如何只评估一侧(在本例中为-rightExpression),但-[NSExpression expressionValueWithObject:context:] 可能会对您有所帮助。我不知道,因为我不确定“上下文”参数的用途。它似乎不像是替代字典,但我可能错了。

因此,如果这不起作用(我不知道它是否会起作用),您可以使用我的解析器:DDMathParser。它有一个解析器,类似于NSPredicate 的解析器,但专门针对解析和评估数学表达式进行了调整。在你的情况下,你会这样做:

#import "DDMathParser.h"

NSString *s = @"$profit2011 / $revenue2011";
NSDictionary *values = ...; // the values of the variables
NSNumber *profitability = [s numberByEvaluatingStringWithSubstitutions:values];

DDMathParseris quite extensive 的文档,它可以做很多事情。


编辑动态变量分辨率

我刚刚推送了一个允许 DDMathParser 动态解析函数的更改。了解函数与变量不同是很重要的。评估一个函数,而简单地替换一个变量。但是,此更改仅对 函数 进行动态解析,而不是变量。没关系,因为 DDMathParser 有一个叫做 argumentless functions 的简洁的东西。

无参数函数是一个函数名,后面没有左括号。为方便起见,它已为您插入。这意味着@"pi" 被正确解析为@"pi()"(因为 π 的常量是作为函数实现的)。

在你的情况下,你可以这样做:

不用正则表达式来生成变量,只需使用术语的名称:

@"profit_2011 / revenue_2011";

这将被解析为就像您输入了:

@"divide(profit_2011(), revenue_2011())"

您可以使用函数解析器设置您的DDMathEvaluator 对象。在DDMathParser 存储库中有两个这样的例子:

    This example 展示了如何使用解析器函数在替换字典中查找“缺失”函数(这将最符合您的要求) This example 向您展示如何解释任何缺失的函数,就好像它评估为 42。

一旦实现了解析器函数,您就可以不必将所有变量打包到字典中。

【讨论】:

嗨,戴夫,我检查了你的解析器。它看起来很有趣,因为它似乎支持 ARC 和 ios 5。你打算支持关于变量替换的块吗?在您当前的实现中,您需要首先构建一个变量字典。使用块可以动态检索变量(例如从核心数据模型)。在我看来,这将是对您的解析器的一个很好的补充。 @AlexR 实际上,它已经可以做到这一点,只需做一些工作。我最近为一位朋友对其进行了调整,因此我将把该代码推送到网上,然后编辑我的帖子。 这会很棒!您能否允许变量包含 _(下划线)字符以便更好地分隔它们的“维度”(例如 $Revenue_2011 或 $Example_-10)?在我看来,这将非常方便。 @AlexR 是的,_ 是变量和函数名称的有效字符。 - 不是变量名中的有效字符,因为不可能将其与减法区分开来。 $Example_-10 是名为“Example_-10”的变量,还是“Example_ 减 10”?我的解析器会将其解析为后者。 在我的示例中,它是 Example_-10,它标识属性“Example”和“-10”(例如,用于标识字典中的时间段)。【参考方案2】:

Is there a better way to do it in your view?

- 使用 Flex & Bison

也许您可以使用正则表达式实现您想要的 - 但对于许多表达式语法,正则表达式不足以解析语法。此外,像这样的正则表达式会变得很大、不可读和不灵活。

您可以使用 Flex(词法分析器)和 Bison(解析器)为您的表达式创建语法定义,并生成 C 代码(我相信您知道,它与 Objective-C 完美配合,因为 Objective- C 是 C),您可以使用它来解析您的表达式。

In the formula, how would you replace the variables by their values?

当你用 Bison 解析它时,你应该有一个带有变量名及其当前值的哈希表。生成语法树时,将对变量的引用添加到语法树节点。

How would you parse the formula?

再一次 - Flex & Bison 专门用于做这种事情 - 他们擅长于此。

【讨论】:

嗨史蒂夫,谢谢! Flex & Bison 在我看来有点矫枉过正。我正在寻找更轻量级的 Objective-C 解决方案。 @AlexR - 一个小的语法会产生小的 C 代码 - 它会非常快。我强烈建议试一试。 我知道一开始它可能会令人生畏 - 但一旦你进入它,它并没有那么糟糕。 真的 - 它的明确目的是生成能够解析你想要解析的各种事物的 C 代码。

以上是关于使用 NSPredicate 解析带有变量的公式的主要内容,如果未能解决你的问题,请参考以下文章

创建一个带有换行符的 NSPredicate 作为字符串的一部分

在带有变量的javascript中使用haversine公式

Swift NSPredicate SUBQUERY with NOT IN?

带有核心数据对象的 NSPredicate

带有 NSPredicate 的 NSFetchedResultsController 未更新

带有特殊字符的 NSPredicate 过滤