首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将实例输出转换为可求值的函数?

如何将实例输出转换为可求值的函数?
EN

Stack Overflow用户
提问于 2014-01-19 12:53:34
回答 2查看 751关注 0票数 5

我使用instaparse解析最终用户使用的一种简单的查询语言,该语言的计算结果为布尔值,例如“(年龄>35岁)和(性别=”男性“)”,然后需要将该查询应用于数千行数据,以确定每一行是否符合表达式。

我的问题是,怎样才能最好地将instaparse的输出转换为随后针对每一行进行评估的函数?上面的查询将被转换为类似的

fn MALE)

请注意,我是一只土豆泥.

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-01-19 14:07:50

您可以为查询语言编写一个小型编译器,使用instaparse来生成一个解析树,使用常规Clojure函数将其转换为Clojure代码,最后使用eval生成一个Clojure函数,然后可以应用到您的记录中。

eval的初始调用会比较昂贵,但其结果函数将等同于在源文件中手工编写的函数,并且不影响性能。事实上,这是eval罕见的有效用例之一--生成了一个函数,其代码是以一种真正动态的方式构造的,然后会被大量调用。

显然,在遵循这种方法时,您需要确保您不会无意中允许不受信任的源执行任意代码。

为了演示,下面是一个基于非常简单的语法的实例解析器,它只能够解析示例查询:

代码语言:javascript
复制
(def p (insta/parser "

expr = and-expr | pred
and-expr = <'('> expr <')'> ws? <'AND'> ws? <'('> expr <')'>
pred = (atom ws? rel ws? atom)
rel = '<' | '>' | '='
atom = symbol | number | string
symbol = #'[A-Z]+'
string = <'\"'> #'[A-Za-z0-9]+' <'\"'>
number = #'\\d+'
<ws> = <#'\\s+'>

"))

对于示例查询,这将生成以下解析树:

代码语言:javascript
复制
[:expr
 [:and-expr
  [:expr
   [:pred [:atom [:symbol "AGE"]] [:rel ">"] [:atom [:number "35"]]]]
  [:expr
   [:pred
    [:atom [:symbol "GENDER"]]
    [:rel "="]
    [:atom [:string "MALE"]]]]]]

现在,我们可以编写一个multimethod,在收集符号时将其转换为Clojure表达式;此处的ctx参数意味着它是一个原子,包含到目前为止遇到的一组符号:

代码语言:javascript
复制
(defmulti expr-to-sexp (fn [expr ctx] (first expr)))

(defmethod expr-to-sexp :symbol [[_ name] ctx]
  (let [name (clojure.string/lower-case name)
        sym  (symbol name)]
    (swap! ctx conj sym)
    sym))

(defmethod expr-to-sexp :string [[_ s] ctx]
  s)

(defmethod expr-to-sexp :number [[_ n] ctx]
  (Long/parseLong n))

(defmethod expr-to-sexp :atom [[_ a] ctx]
  (expr-to-sexp a ctx))

(defmethod expr-to-sexp :rel [[_ name] ctx]
  (symbol "clojure.core" name))

(defmethod expr-to-sexp :pred [[_ left rel right] ctx]
  (doall (map #(expr-to-sexp % ctx) [rel left right])))

(defmethod expr-to-sexp :and-expr [[_ left right] ctx]
  `(and ~(expr-to-sexp left ctx) ~(expr-to-sexp right ctx)))

(defmethod expr-to-sexp :expr [[_ child] ctx]
  (expr-to-sexp child ctx))

让我们将其应用到示例解析树中:

代码语言:javascript
复制
(expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") (atom #{}))
;= (clojure.core/and (clojure.core/> age 35) (clojure.core/= gender "MALE"))

(let [ctx (atom #{})]
  (expr-to-sexp (p "(AGE > 35) AND (GENDER = \"MALE\")") ctx)
  @ctx)
;= #{age gender}

最后,下面是一个使用上面的函数来构建Clojure函数的函数:

代码语言:javascript
复制
(defn compile-expr [expr-string]
  (let [expr (p expr-string)
        ctx  (atom #{})
        body (expr-to-sexp expr ctx)]
    (eval `(fn [{:keys ~(vec @ctx)}] ~body))))

您可以这样使用它:

代码语言:javascript
复制
(def valid? (compile-expr "(AGE > 35) AND (GENDER = \"MALE\")"))

(valid? {:gender "MALE" :age 36})
;= true

(valid? {:gender "FEMALE" :age 36})
;= false
票数 9
EN

Stack Overflow用户

发布于 2014-01-19 15:35:40

我不确定我是否理解你的问题。然而,我猜想:)

您可以使用clojure的"filter“函数来还原一个集合。我不熟悉instaparse,所以我将模拟一些测试数据(收集):

代码语言:javascript
复制
(def ls  [{:age 10, :gender "m"} {:age 15 :gender "fm"}] )

所以"ls“是我们的收藏。为了将集合限制在性别值为"m“且年龄值大于5的元素上,我们应用以下筛选器:

代码语言:javascript
复制
(filter (fn [a] (if (and (= (:gender a) "m") (> (:age a) 5)) a)) ls)

结果是:

代码语言:javascript
复制
 ({:age 10, :gender "m"})

也可以将过滤器"fn“表示为匿名函数:

代码语言:javascript
复制
(filter #(if (and (= (:gender %1) "m") (> (:age %1) 5)) %1) ls)

希望这能有所帮助。注意!如果你可以发布一个你想要处理的“结果数据”的样本,那会有很大的帮助:)

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21216991

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档