使用 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:
,您只需返回YES
或NO
值,因为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];
DDMathParser
is 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?