【问题标题】:Nested map statements in Clojure don't evaluate properly, seems related to LazySeqClojure 中的嵌套映射语句无法正确评估,似乎与 LazySeq 有关
【发布时间】:2016-05-21 14:41:52
【问题描述】:

我在 Clojure 中编写了一个函数,它应该采用逻辑表达式并返回一个等效表达式,其中所有 not 语句都直接作用于变量,如下所示:

(not (and p q r))

变成

(or (not p) (not q) (not r))

它使用德摩根定律将nots 向内推,如果not 直接作用于另一个not 语句,它们就会取消。代码如下所示:

(defn transform [expr]
    (if
        (list? expr)
        (if
            (=
                'not
                (first expr)
            )
            (if
                (list? (nth expr 1))
                (if
                    (=
                        'not
                        (first (nth expr 1))
                    )
                    (transform (first (rest (first (rest expr)))))
                    (if
                        (=
                            'and
                            (first (nth expr 1))
                        )
                        (cons 
                            'or 
                            (map
                                transform
                                (map
                                    not-ify
                                    (rest (first (rest expr)))
                                )
                            )
                        )
                        (if
                            (=
                                'or
                                (first (nth expr 1))
                            )
                            (cons
                                'and 
                                (map
                                    transform
                                    (map
                                        not-ify
                                        (rest (first (rest expr)))
                                    )
                                )
                            )
                            expr
                        )
                    )
                )
                expr
            )
            expr
        )
        expr
    )
)

问题出在这部分:

(map
    transform
    (map
        not-ify
        (rest (first (rest expr)))
    )
)

第一个map 语句使用函数not-ify(请原谅双关语)基本上在每个语句之前放置一个not。那部分有效。但是,输出不适用于map transform,尽管map transform 部分本身可以工作。让我告诉你:

如果我在 REPL 中写下以下内容:

(def expr '(not (and q (not (or p (and q (not r)))))))


(map
    not-ify
    (rest (first (rest expr)))
)

我得到了输出((not q) (not (not (or p (and q (not r))))))

如果我随后获取该输出并运行(map transform '((not q) (not (not (or p (and q (not r))))))),我将得到输出((not q) (or p (and q (not r))))。到目前为止一切顺利。

但是,如果我一次运行它,像这样:

(map
    transform
    (map
        not-ify
        (rest (first (rest expr)))
    )
)

我得到的是这个输出:((not q) (not (not (or p (and q (not r))))))

如果运行

(def test1
    (map
        not-ify
        (rest (first (rest expr)))
    )
)
(map transform test1)

我也收到了((not q) (not (not (or p (and q (not r))))))

但是如果我跑了

(def test2 '((not q) (not (not (or p (and q (not r)))))))
(map transform test2)

我再次得到正确的结果:((not q) (or p (and q (not r))))

我的猜测是,这在某种程度上与 map not-ify 输出 (test1) 的类型为 LazySeq 有关,而如果我手动输入输入 (test2),它会变成 PersistentList。我尝试在test1 上运行(into (list)) 将其转换为PersistentList,以及doRundoAll,但没有结果。我能否以某种方式阻止我的 map not-ify 语句返回 LazySeq

【问题讨论】:

  • map 将始终返回一个lazyseq。
  • 你对我如何解决这个问题有什么建议吗?不能嵌套map 语句吗?
  • notify是这样定义的吗?:(defn not-ify [expr] (list 'not expr))

标签: clojure nested boolean-logic lazy-sequences map-function


【解决方案1】:

简短的回答是使用seq? 而不是list?

这是我将如何实现它:

(defn push-not-down [expr]
  (if (and (seq? expr) (seq? (second expr)))
    (let [[outer-op & [[inner-op & inner-args] :as outer-args] :as expr] expr]
      (if (= 'not outer-op)
        (condp = inner-op
          'and (cons 'or (map #(push-not-down (list 'not %)) inner-args))
          'or (cons 'and (map #(push-not-down (list 'not %)) inner-args))
          'not (first inner-args)
          expr)
        (if (#{'or 'and} outer-op)
          (cons outer-op (map push-not-down outer-args))
          expr)))
    expr))

(deftest push-not-down-test
  (testing "Not or and not and are transformed to and not and or not"
    (is (= '(or (not :a) (not :b))
           (push-not-down
             '(not (and :a :b)))))
    (is (= '(and (not :a) (not :b))
           (push-not-down
             '(not (or :a :b))))))
  (testing "Double nots cancel"
    (is (= :a
           (push-not-down
             '(not (not :a))))))
  (testing "The rules work together in complex combinations"
    (is (= '(and :a (and :b (not :c)))
           (push-not-down
             '(and (not (not :a)) (not (or (not :b) :c))))))
    (is (= '(or (or (and (not :a))))
           (push-not-down
             '(or (or (and (not :a))))))))
  (testing "Nested expressions that don't fit the rules are preserved"
    (is (= '(not (inc 1))
           (push-not-down
             '(not (inc 1)))))
    (is (= '(inc (or 2 1))
           (push-not-down
             '(inc (or 2 1)))))))

对于形式和表达式,列表或序列之间没有显着差异。或者,如果您确实想保留列表,您只需要更彻底地将序列转换为列表:)

【讨论】:

  • 谢谢!这真的帮助我翻山越岭。您的示例看起来很棒,不幸的是我不太了解 Clojure;碰巧我的离散数学课程有 Clojure 中的计算机作业,而不仅仅是基础教程,所以你的一半代码对我来说看起来像是胡言乱语。 :P
【解决方案2】:

为了它的价值,......

首先,让我们定义逻辑inverse 变成什么:

(def opposite {'and 'or, 'or 'and})

(defn inverse [expr]
  (let [default (list 'not expr)]
    (if (seq? expr)
      (let [[op & args] expr]
        (if (= op 'not)
          (first args)
          (cons (opposite op) (map inverse args))))
      default)))

让我们测试一下:

(map inverse ['p '(not p) '(and a b) '(and (not a) b)])
;((not p) p (or (not a) (not b)) (or a (not b)))

它可以短路双重否定以及做DeMorgan 的事情。

现在我们可以表达变换了:

(defn transform [expr]
  (if (seq? expr)
    (let [[op & args] expr]
      (if (= op 'not)
        (inverse (first args))
        (cons op (map transform args))))
    expr))

例如,

(transform '(not (and p q r)))
;(or (not p) (not q) (not r))
  • 在找到not 的地方,它会返回参数的inverse
  • 否则,它会重构表达式,转换 可以使用的子表达式,就像 inverse 所做的那样。

如果我们不关心转换子表达式,我们可以简化为

(defn transform [expr]
  (or (and (seq? expr)
           (let [[op & args] expr]
             (if (= op 'not) (inverse (first args)))))
      expr))

  • 我发现拉出inverse 操作更容易理解。
  • 在简单的transform 中,我玩过andor 和 单臂if 避免重蹈覆辙 记下 OP 的文字。

这与@TimothyPratley's answer 有何关系?

  • 我无耻地窃取了他关于使用seq? 而不是 list?.
  • 他在无法识别的操作上引发异常。我的只是做 他们nil
  • 我已将ands 和ors 合并为一个案例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-18
    • 1970-01-01
    • 2014-04-30
    • 1970-01-01
    相关资源
    最近更新 更多