Используйте NSPredicate для анализа формулы с переменными - PullRequest
2 голосов
/ 22 февраля 2012

Задача:

Я планирую проанализировать строку формулы в NSPredicate и заменить переменные в строке их числовыми значениями.Переменные - это имена свойств существующих экземпляров объектов в моей модели данных, например, у меня есть класс «company» с экземпляром «Apple Corp.»

Настройка:

Моя формула должна выглядеть следующим образом: "Profitability_2011_in% = [Прибыль 2011] / [Доход 2011]"

Экземпляр «Apple Corp» будет иметь следующие свойства:

Доход 2009 = 10, Доход 2010 = 20, Доход 2011 = 30, Прибыль 2009 = 5, Прибыль 2010 = 10, Прибыль 2011 = 20.

Следовательно, формула даст 20/30 = 67%.

Переменные, как правило, являются двумерными, например, определяемыми как «прибыль» как позиция финансового отчета и «год» (например, 2011 год).).Переменные заключены в [], а размеры разделены "" (пробел).

Как бы я это сделал

Моя реализация началась бы с NSRegularExpression's matchesInString:options:range: чтобы получить массив всех переменных в формуле (Прибыль 2011, Доход 2011), а затем создать NSDictionary (ключ = имя переменной) из этого массива, запросив мою модель данных.

Что делатьвы думаете?

  • Есть ли лучший способ сделать это, на ваш взгляд?
  • В формуле, как бы вы заменили переменные на их значения?
  • Как бы вы проанализировали формулу?

Спасибо !!

Ответы [ 2 ]

1 голос
/ 22 февраля 2012

Да, вы можете сделать это. Это относится к категории «Использование NSPredicate для вещей, для которых оно не было предназначено», но будет работать просто отлично.

Вам необходимо заменить переменные одним словом, которое начинается с $, поскольку NSPredicate обозначает переменные:

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

Как бы ты не хотел это делать, отлично. NSRegularExpression это прекрасный способ сделать это.

Как только вы это сделаете, у вас будет что-то вроде этого:

@"$profitability2011 = $profit2011 / $revenue2011"

Затем вы можете вставить это через +predicateWithFormat:. Вы получите обратно NSComparisonPredicate. -leftExpression будет иметь тип NSVariableExpressionType, а -rightExpression будет иметь тип NSFunctionExpressionType.

Вот тут-то и начинают волосатые вещи. Если бы вы были -evaluteWithObject:substitutionVariables:, вы бы просто вернули значение YES или NO, поскольку предикат - это просто утверждение, которое оценивается как true или false . Я не исследовал, как вы могли бы просто оценить одну сторону (в данном случае -rightExpression), но возможно, что -[NSExpression expressionValueWithObject:context:] может вам помочь. Я не знаю, потому что я не уверен, для чего этот параметр "context". Это не кажется как словарь подстановки, но я могу ошибаться.

Так что, если это не сработает (и я понятия не имею, будет ли это или нет), вы можете использовать мой парсер: DDMathParser . У него есть синтаксический анализатор, похожий на синтаксический анализатор NSPredicate, но он специально настроен для анализа и вычисления математических выражений. В вашем случае вы бы сделали:

#import "DDMathParser.h"

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

Документация для DDMathParser довольно обширна , и она может сделать совсем немного.


edit Разрешение динамической переменной

Я только что выдвинул изменение, которое позволяет DDMathParser динамически разрешать функции. Важно понимать, что функция отличается от переменной. Функция оценивается, а переменная просто подставляется. Однако изменение влияет только на динамическое разрешение для функций , а не переменных. Это нормально, потому что в DDMathParser есть такая замечательная вещь, которая называется без аргументов .

Функция без аргументов - это имя функции, за которым не следует открывающая скобка. Для удобства он вставлен для вас. Это означает, что @"pi" правильно проанализирован как @"pi()" (поскольку константа для & pi; реализована как функция).

В вашем случае вы можете сделать это:

Вместо регулярного выражения вашей строки для создания переменных просто используйте имена терминов:

@"profit_2011 / revenue_2011";

Это будет проанализировано, как если бы вы ввели:

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

Вы можете настроить объект DDMathEvaluator с помощью функции распознавателя. В хранилище DDMathParser есть два примера:

  1. В этом примере показано, как использовать функцию распознавателя для поиска «отсутствующей» функции в словаре подстановки (это будет наиболее похоже на то, что вы хотите)
  2. В этом примере показано, как интерпретировать любую отсутствующую функцию, как если бы она была оценена как 42.

Как только вы реализуете функцию распознавателя, вы можете отказаться от упаковки всех ваших переменных в словарь.

1 голос
/ 22 февраля 2012

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 специально предназначены для подобных вещей - и они преуспели в этом.

...