Этот ответ будет охватывать многие из тех же элементов, что и существующие ответы, но эта проблема (передающая имена столбцов для функций) возникает достаточно часто, и я хочу, чтобы там был ответ, который охватывал вещи немного более всесторонне.
Предположим, что у нас очень простой фрейм данных:
dat <- data.frame(x = 1:4,
y = 5:8)
, и мы хотели бы написать функцию, которая создает новый столбец z
, который представляет собой сумму столбцов x
и y
.
Очень распространенный камень преткновения здесь состоит в том, что естественная (но некорректная) попытка часто выглядит так:
foo <- function(df,col_name,col1,col2){
df$col_name <- df$col1 + df$col2
df
}
#Call foo() like this:
foo(dat,z,x,y)
Проблема здесь в том, что df$col1
t оценить выражение col1
. Он просто ищет столбец в df
, буквально называемый col1
. Это поведение описано в ?Extract
в разделе «Рекурсивные (похожие на список) объекты».
Самое простое и наиболее часто рекомендуемое решение - это просто переключиться с $
на [[
и передать аргументы функции как строки:
new_column1 <- function(df,col_name,col1,col2){
#Create new column col_name as sum of col1 and col2
df[[col_name]] <- df[[col1]] + df[[col2]]
df
}
> new_column1(dat,"z","x","y")
x y z
1 1 5 6
2 2 6 8
3 3 7 10
4 4 8 12
Это часто считается «лучшей практикой», так как это самый сложный метод. Передача имен столбцов в виде строк примерно такая же однозначная, как вы можете получить.
Следующие два варианта более продвинутые. Многие популярные пакеты используют эти методы, но использование их хорошо требует большей осторожности и умения, поскольку они могут вводить тонкие сложности и непредвиденные точки отказа. Этот раздел книги Хэдли Advanced R является отличной ссылкой для некоторых из этих проблем.
Если вы действительно хотите сохранить пользователя от ввода всех этих кавычки, одним из вариантов может быть преобразование голой, некотируемых имен столбцов в строки с помощью deparse(substitute())
:
new_column2 <- function(df,col_name,col1,col2){
col_name <- deparse(substitute(col_name))
col1 <- deparse(substitute(col1))
col2 <- deparse(substitute(col2))
df[[col_name]] <- df[[col1]] + df[[col2]]
df
}
> new_column2(dat,z,x,y)
x y z
1 1 5 6
2 2 6 8
3 3 7 10
4 4 8 12
Это, откровенно говоря, немного глупо, возможно, так как мы действительно делаем то же самое, что и в new_column1
, просто с кучей дополнительной работы, чтобы преобразовать простые имена в строки.
Наконец, если мы хотим получить действительно фантазию, мы можем решить, что вместо того, чтобы проходить в именах добавляемых двух столбцов мы хотели бы быть более гибкими и допускать другие комбинации двух переменных. В этом случае мы, скорее всего, прибегнем к использованию eval()
в выражении, включающем два столбца:
new_column3 <- function(df,col_name,expr){
col_name <- deparse(substitute(col_name))
df[[col_name]] <- eval(substitute(expr),df,parent.frame())
df
}
Просто для удовольствия я все еще использую deparse(substitute())
для имени нового столбца , Здесь будет работать все следующее:
> new_column3(dat,z,x+y)
x y z
1 1 5 6
2 2 6 8
3 3 7 10
4 4 8 12
> new_column3(dat,z,x-y)
x y z
1 1 5 -4
2 2 6 -4
3 3 7 -4
4 4 8 -4
> new_column3(dat,z,x*y)
x y z
1 1 5 5
2 2 6 12
3 3 7 21
4 4 8 32
Итак, короткий ответ в основном: передать имена столбцов data.frame в виде строк и использовать [[
для выбора отдельных столбцов. Только начинайте разбираться в eval
, substitute
и т. Д., Если вы действительно знаете, что делаете.
Я настоятельно рекомендую язык F # в качестве предпочтительного языка для анализа на платформе .NET. Его корни в семействе языков ML означают, что он имеет отличную поддержку языкового программирования.
Дискриминационные объединения и сопоставление с образцом позволяют создать очень сжатую и мощную спецификацию вашего AST. Функции высшего порядка позволяют определять операции синтаксического анализа и их состав. Первоклассная поддержка монадических типов позволяет неявно обрабатывать управление состоянием, значительно упрощая состав синтаксических анализаторов. Мощный вывод типов очень помогает при определении этих (сложных) типов. И все это можно указать и выполнить в интерактивном режиме, что позволяет быстро создавать прототипы.
Стефан Толксдорф применил это на практике с помощью своей библиотеки комбинаторов синтаксического анализатора FParsec
Из его примеров мы видим, насколько естественно определяется AST:
type expr =
| Val of string
| Int of int
| Float of float
| Decr of expr
type stmt =
| Assign of string * expr
| While of expr * stmt
| Seq of stmt list
| IfThen of expr * stmt
| IfThenElse of expr * stmt * stmt
| Print of expr
type prog = Prog of stmt list
реализация синтаксического анализатора (частично исключена) столь же лаконична :
let stmt, stmtRef = createParserForwardedToRef()
let stmtList = sepBy1 stmt (ch ';')
let assign =
pipe2 id (str ":=" >>. expr) (fun id e -> Assign(id, e))
let print = str "print" >>. expr |>> Print
let pwhile =
pipe2 (str "while" >>. expr) (str "do" >>. stmt) (fun e s -> While(e, s))
let seq =
str "begin" >>. stmtList .>> str "end" |>> Seq
let ifthen =
pipe3 (str "if" >>. expr) (str "then" >>. stmt) (opt (str "else" >>. stmt))
(fun e s1 optS2 ->
match optS2 with
| None -> IfThen(e, s1)
| Some s2 -> IfThenElse(e, s1, s2))
do stmtRef:= choice [ifthen; pwhile; seq; print; assign]
let prog =
ws >>. stmtList .>> eof |>> Prog
Например, во второй строке stmt
и ch
- парсеры, а sepBy1
- комбинатор монадического парсера, который принимает два простых парсера и возвращает комбинированный парсер. В этом случае sepBy1 p sep
возвращает синтаксический анализатор, который анализирует одно или несколько вхождений p
, разделенных sep
. Таким образом, вы можете увидеть, как быстро мощный синтаксический анализатор можно объединить с простыми синтаксическими анализаторами. Поддержка F # переопределенных операторов также позволяет использовать краткую инфиксную нотацию, например комбинатор последовательности и комбинатор выбора могут быть указаны как >>.
и <|>
.
Удачи,
Дэнни
JFlex является реализацией гибкого провода для Java, и существует теперь порт C# того проекта http://sourceforge.net/projects/csflex/ . Также, кажется, существует порт C# происходящего КУБКА, который может быть найден здесь: http://sourceforge.net/projects/datagraph/
я также рекомендовал бы избежать руки, обрабатывающей Ваше собственное решение. Я попробовал это однажды сам для очень простого языка (часть университетского проекта), и это было невероятно трудоемким и трудным. Также чрезвычайно трудно поддержать и измениться когда-то записанный.
Используя существующий парсер-генератор способ пойти, поскольку объем тяжелой работы был сделан и был хорошо протестирован за эти годы.
Взгляд gplex и gppg, лексический анализатор и парсеры-генераторы для.NET. Они работают хорошо и основаны на том же (и почти совместимый) вход как закон и yacc, который относительно прост в использовании.
Я также голосовал бы за использование существующего синтаксического анализатора + лексический анализатор.
единственная причина I видит в выполнении его вручную:
Причина Вы не хотите таблично-управляемый синтаксический анализатор, состоит в том, что Вы не сможете создать разумные сообщения об ошибках. Это хорошо для сгенерированного языка, но не того, где люди вовлечены. Сообщения об ошибках, произведенные подобными языку C компиляторами, представляют достаточные свидетельства, которые люди могут адаптировать к чему-либо, неважно, как плохо.
Хорошо, если Вы не возражаете использовать другой инструмент компилятора компилятора как ANTLR, я предлагаю, чтобы Вы смотрели на Coco/R
, я использовал его в прошлом, и это было довольно хорошо...
На вашем месте я сделал бы, чтобы другие пошли в ANTLRv3 с помощью ANTLRWorks GUI, который дает Вам очень удобный способ протестировать Вашу грамматику. Мы используем ANTLR в нашем проекте и хотя кривая обучения может быть немного крутой в начале, после того как Вы узнаете, что это довольно удобно. Также на их электронной новостной рассылке существует много людей, которые очень услужливы.
пз. IIRC у них также есть грамматика SQL, на которую Вы могли смотреть.
hth
В C/Unix традиционный путь состоит в том, чтобы использовать закон и yacc. С GNU эквивалентные инструменты являются гибким проводом и бизоном. Я не знаю для Windows/C#.
Если Вы хотите записать, что это вручную, рекурсивный достойный является самый разумный способ пойти.
Вы могли использовать синтаксический анализатор таблицы, но это будет чрезвычайно трудно поддержать.
Пример:
Data = Object | Value;
Value = Ident, '=', Literal;
Object = '{', DataList, '}';
DataList = Data | DataList, Data;
ParseData {
if PeekToken = '{' then
return ParseObject;
if PeekToken = Ident then
return ParseValue;
return Error;
}
ParseValue {
ident = TokenValue;
if NextToken <> '=' then
return Error;
if NextToken <> Literal then
return Error;
return(ident, TokenValue);
}
ParseObject {
AssertToken('{');
temp = ParseDataList;
AssertToken('}');
return temp;
}
ParseDataList {
data = ParseData;
temp = []
while Ok(data) {
temp = temp + data;
data = ParseData;
}
}
Синтаксические анализаторы с рекурсивным спуском являются действительно лучшими, возможно, только, синтаксические анализаторы, которые могут быть созданы вручную. Необходимо будет все еще снять с костей на том, что точно формальный, контекстно-свободный язык является и поместил язык в нормальную форму. Я лично предложил бы, чтобы Вы удалили левую рекурсию и поместили Ваш язык в Нормальная форма Greibach . Когда Вы делаете это, синтаксический анализатор примерно пишет себя.
, Например, это производство:
A => aC
A => bD
A => eF
становится чем-то простым как:
int A() {
chr = read();
switch char
case 'a': C();
case 'b': D();
case 'e': F();
default: throw_syntax_error('A', chr);
}
И нет никаких намного более трудных случаев здесь (что является более трудным, удостоверяются, что Ваша грамматика находится в точно правильной форме, но это позволяет Вам управление, которое Вы упомянули).
Ссылка Anton , также кажется превосходным.
Рекурсивный спуск даст Вам самый простой способ пойти, но я должен был бы согласиться с mouviciel что гибкий провод и бизон и определенно стоящий изучения. Когда Вы узнаете, что у Вас есть ошибка в Вашей грамматике, исправление определения языка в гибком проводе / бизон будет адской партией, легче затем перезапись Вашего кода рекурсивного спуска.
к вашему сведению синтаксический анализатор C# является записанным рекурсивным спуском, и он имеет тенденцию быть довольно устойчивым.
Единственный вид синтаксического анализатора, который может быть написан от руки нормальным человеком, является рекурсивным спуском. Тем не менее запись восходящего синтаксического анализатора вручную все еще возможна, но очень нежелательна.
, Если Вы подлежите синтаксическому анализатору RD, необходимо проверить, что грамматика SQL не является леворекурсивной (и устраните рекурсию при необходимости), и затем в основном запишите функцию для каждого грамматического правила. См. это для дальнейшей ссылки.
Добавление моей речи к хору в пользу рекурсивного спуска (LL1). Они просты, быстро, и IMO, нисколько трудно для поддержания.
Однако бросают хороший взгляд на Ваш язык, чтобы удостовериться, что это - LL1. Если у Вас есть какой-либо синтаксис как C, имеет, как ((((тип)) нечто) []) , где Вам, возможно, придется убывать несколько слоев круглых скобок перед ровным обнаружением, смотрите ли Вы на тип, переменную или выражение, то LL1 будет очень трудными, и восходящими победами.
Я предлагаю, чтобы Вы не писали лексический анализатор вручную - гибкий провод использования или подобный. Задача распознавания маркеров не состоит в том, что трудно, чтобы сделать вручную, но я не думаю, что Вы получили бы много.
, Поскольку другие сказали, синтаксические анализаторы с рекурсивным спуском является самым легким записать вручную. Иначе необходимо поддержать таблицу изменений состояния для каждого маркера, который не действительно человекочитаем.
я - вполне уверенные реализации ANTLR синтаксический анализатор с рекурсивным спуском так или иначе: существует упоминание о нем в интервью о ANTLR 3.0.
я также нашел серию сообщений в блоге приблизительно запись синтаксического анализатора в C#. Это кажется довольно нежным.
Рискуя оскорбить OP, писать синтаксический анализатор для большого языка, такого как SQL какого-либо конкретного поставщика, вручную, когда доступны хорошие инструменты генератора синтаксического анализатора (такие как ANTLR), просто безумие. Вы потратите гораздо больше времени, переписывая свой синтаксический анализатор вручную, чем исправляя «крайние случаи» с помощью генератора синтаксического анализатора, и вам неизбежно придется возвращаться и пересматривать синтаксический анализатор в любом случае по мере продвижения стандартов SQL или вы обнаружите, что неправильно поняли что-то другое. Если вы недостаточно хорошо разбираетесь в своей технологии синтаксического анализа, потратьте время на ее понимание. Вам не понадобятся месяцы, чтобы понять, как справиться с крайними случаями с генератором синтаксического анализатора, и вы уже признали это, что готовы потратить месяцы, делая это вручную.
Сказав это, если вы это делаете одержим переделкой вручную,
Не существует "единственного лучшего" пути. В зависимости от ваших потребностей вам может понадобиться восходящий (LALR1) или рекурсивный спуск (LLk). В статьях , подобных этой , приводятся личные причины предпочтения LALR (1) (снизу вверх) перед LL (k). Однако у каждого типа парсера есть свои преимущества и недостатки. Обычно LALR будет быстрее, поскольку машина с конечным числом состояний генерируется как таблица поиска.
Чтобы выбрать то, что подходит вам, исследуйте вашу ситуацию; ознакомьтесь с инструментами и технологиями. Начинать с некоторых статей Википедии LALR и LL - неплохой выбор. В обоих случаях ВСЕГДА следует начинать с указания грамматики в BNF или EBNF . Я предпочитаю EBNF за его лаконичность.
После того, как вы сосредоточились на том, что вы хотите сделать и как представить это в виде грамматики, (BNF или EBNF) попробуйте несколько различных инструментов и запустите их по репрезентативным образцам текста для анализа.
Как ни странно:
Однако я слышал, что LL (k) более гибкий. Я никогда не удосужился выяснить это для себя.Из своего небольшого опыта создания синтаксического анализатора я заметил, что, независимо от того, является ли это LALR или LL (k), лучший способ выбрать то, что лучше всего подходит для ваших нужд, - это начать с написания грамматики. Я написал свою собственную библиотеку шаблонов построителя синтаксического анализатора C ++ EBNF RD, использовал Lex / YACC и написал небольшой синтаксический анализатор R-D. Это было растянуто на большую часть 15 лет, и я потратил не более 2 месяцев на самый длинный из трех проектов.