如何在Clojure中实现决策矩阵/表

时间:2014-04-04 13:17:36

标签: clojure

需要实施如下的决策表:

MemberType       Amount      => Discount
"Guest"          > 2000      => 3%
"Silver"         any         => 5%
"Silver"         > 1000      => 10%
"Gold"           any         => 15%
"Gold"           > 500       => 20%

我想,如果在Clojure中正确实现,我们可以定义一个规则表,如下所示:

(defrule calc-discount
  [member-type   amount] 
  "Guest"        (greater-than 2000) => 0.03
  "Silver"       (anything)          => 0.05
  "Silver"       (greater-than 1000) => 0.1
  "Gold"         (anything)          => 0.15
  "Gold"         (greater-than 500)  => 0.2
 )

当然,应该有更好的方法来编写/定义这样的规则集。然而,我认为关键是如何定义“defrule”来实现这一目标?

3 个答案:

答案 0 :(得分:8)

使用core.match!它是用于模式匹配的Clojure库。

你的例子会变成这样的东西......

(let [member-type "Gold"
      amount 600]
  (match [member-type amount]
         ["Guest" (_ :guard #(> % 2000))] 0.03
         ["Silver" (_ :guard #(> % 1000))] 0.1
         ["Silver" _] 0.05
         ["Gold" (_ :guard #(> % 500))] 0.2
         ["Gold" _] 0.15
      :else 0))

 ; => 0.2

答案 1 :(得分:4)

对于此示例,您可以使用condp来简明扼要地表达业务逻辑。

(defn discount 
  [member-type amount] 
  (condp (fn [[type tier] _] (and (= member-type type) (> amount tier))) nil 
    ["Guest"  2000] 0.03 
    ["Silver" 1000] 0.10 
    ["Silver"    0] 0.05 
    ["Gold"    500] 0.20
    ["Gold"      0] 0.15 
    0.00))

(discount "Gold" 600) ;=> 0.2

如果您希望在示例中实现语法,则需要编写宏。一个非常粗略的例子:

(defmacro defrule [name fields & clauses]
  (let [exp (fn [f c] (if (list? c) (list* (first c) f (rest c)) (list `= c f)))]
    `(defn ~name ~fields 
       (cond 
         ~@(for [clause (partition-all (+ 2 (count fields)) clauses)
                 form [(cons `and (map exp fields clause)) (last clause)]] 
             form)))))

(def any (constantly true))

(defrule calc-discount
  [member-type amount]
   "Guest"   (> 2000)  => 0.03
   "Silver"  (> 1000)  => 0.10
   "Silver"     (any)  => 0.05
   "Gold"     (> 500)  => 0.20
   "Gold"       (any)  => 0.15)

(calc-discount "Silver" 1234) ;=> 0.10

答案 2 :(得分:0)

您似乎想要的是以表格形式显示条件,及其相应结果的各种组合的可关联的可视表示形式。

检出cond-table宏,该宏将表格形式扩展为常规cond形式: https://github.com/semperos/rankle/blob/master/src/com/semperos/rankle/util.clj

使用这种方法,您的示例可以这样编码:

(defn discount-for [memtype amount]
  (let [spends? (fn [catgy pred] (and (= memtype catgy)
                                       (pred amount)))
        >n (fn [n] #(> % n))
        any #(< 0 % 501)]
    (util/cond-table 

     :| spends?  (>n 2000) (>n 1000) (>n 500) any 
     :| "Guest"   0.03       0        0       0
     :| "Silver"  0.10       0.10     0.05    0.05
     :| "Gold"    0.20       0.20     0.20    0.15)))

(discount-for "Guest" 3000)
;;=> 0.03
(discount-for "Gold" 25.95)
;;=> 0.15