У меня есть идея создать ограниченную форму языка для табличного анализа. Вопрос в том, есть ли уже что-то вроде следующего или есть лучшие идеи для этого. Ключевое требование заключается в том, что пользователь должен иметь возможность использовать голые файлы сценариев без необходимости создавать полноценную программу на Python. OTOH-элементы языка сценариев компилируются во время выполнения и должны предлагать возможность формулировать сложные условия и вычисления (в основном, арифметические и строковые операции). Декларативный способ формулирования программ на языке (см. Ниже) запрещает прямое использование синтаксиса Python в качестве средства для языка (см. Значение функции @PART) - по крайней мере, я так думаю.
Есть ли лучший / более умный / более элегантный способ достижения моих целей, чем программировать в полуразборной смеси Python и неуклюжего самоопределенного синтаксиса, как я делал ниже?
Ниже я попытаюсь прояснить свои идеи на примере. Таблица ввода создается другой программной частью и выглядит так при запуске интерпретации скрипта:
# First,Last,Department,Hourly Wage
[ ('Greg','Foo','HR',100),
('Judy','Bar','EE',51),
('Jake','Baz','HR',75),
('Lila','Bax','HR',49),
('Norm','Fob','EE',49) ]
Ниже приведен сам файл 'script'. Это будет файл для себя в производственной системе. Код программы в настоящее время представлен в виде массива строк Python - возможно, даже не в окончательной версии.
# A program to produce per department the average hourly rate, separated for the higher and lower 50% of earners:
[ "@SORT(2,-3)",
"@SET({max},@MAX({3}))",
"@PART({2}!={^2} or {3}<{max}/2)",
"@SET({dep},@FIRST({2}))",
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))"
]
Я постараюсь шаг за шагом объяснить, что должен делать скрипт:
"@SORT(2,-3)"
сортирует таблицу после столбца 2 (по возрастанию), затем по столбцу 3 (по убыванию). Мы получаем
[ ('Judy','Bar','EE',51),
('Norm','Fob','EE',49),
('Greg','Foo','HR',100),
('Jake','Baz','HR',75),
('Lila','Bax','HR',49),
]
"@SET({max},@MAX({3}))"
берет максимум столбца 3 и помещает его в динамическую локальную переменную max
"@PART({2}!={^2} or {3}<{max}/2)"
немного сложнее. @PART разделяет текущую таблицу
в несколько вложенных таблиц, оценивая данное выражение для каждой строки и обрезая перед строкой, если это правда.
Здесь мы хотим сократить границы департамента (столбец 2). {^ 2} является восходящей ссылкой, то есть элементом в столбце 2
из предыдущего ряда. Этот синтаксис необходим, поскольку я считаю, что возможность разбивать таблицы на более сложные условия
чем «строка отличается от предыдущей строки в X» очень важно (представьте, что вы хотите разбить таблицу на классы с доходом 10 тыс.)
поэтому мне нужна выразительная сила (ограниченного) выражения Python в аргументе PART. Также это имеет значение
что выражение не может быть оценено для первой строки, так как нет предшественника, поэтому функция PART
просто пойдет через это.
После этой функции у нас есть следующие таблицы:
[ ('Judy','Bar','EE',51) ] # Department EE
[ ('Norm','Fob','EE',49) ] # Norm Fob is in the same department but earns less than half of the maximum
[ ('Greg','Foo','HR',100), # New department HR
('Jake','Baz','HR',75) ]
[ ('Lila','Bax','HR',49) ] # HR dept. but less than half of the best earner
С этого момента функции в скрипте будут работать с каждой дополнительной таблицей отдельно. Функция PART более или менее
запускает цикл над всеми полученными вложенными таблицами, и каждая последующая функция (включая больше PART) выполняется
субтаблица в изоляции.
"@SET({dep},@FIRST({2}))",
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))"
@ FIRST ({2}) просто принимает значение столбца 2 первой строки. @SUM ({3}) принимает сумму всего столбца 3
и @CNT ({3}) считает количество строк, столбец 3 которых не равен None.
Я представляю результат функции примерно здесь:
[ ('Judy','Bar','EE',51) ]
"@SET({dep},@FIRST({2}))" --> {dep} = "EE"
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))" --> output "EE 51"
[ ('Norm','Fob','EE',49) ]
"@SET({dep},@FIRST({2}))", --> {dep} = "EE"
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))" --> output "EE 49"
[ ('Greg','Foo','HR',100),
('Jake','Baz','HR',75) ]
"@SET({dep},@FIRST({2}))", --> {dep} = "HR"
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))" --> output "HR 87.5"
[ ('Lila','Bax','HR',49) ]
"@SET({dep},@FIRST({2}))", --> {dep} = "HR"
"@PRINT({dep},float(@SUM({3}))/@CNT({3}))" --> output "HR 49"
Я должен добавить, что предпочел бы, чтобы решение было небольшим, то есть не использовать нестандартные пакеты Python, такие как pyparsing и т. Д.