2016-05-21 3 views
1

Я написал функцию в Clojure, которая должна взять логическое выражение и вернуть эквивалентное выражение, где все операторы not действуют непосредственно на переменные, например:Вложенные операторы карты в Clojure не оцениваются должным образом, похоже на LazySeq

(not (and p q r)) 

становится

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

Он использует законы де Моргана подталкивать not S внутрь, а если 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 работает сама по себе. Позвольте мне показать вам:

Если я пишу следующее в РЕПЛ:

(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. Я попробовал запустить (into (list)) на test1, чтобы преобразовать его в PersistentList, а также doRun и doAll, без каких-либо результатов. Могу ли я как-то остановить мой оператор map not-ify от возвращения LazySeq?

+0

'map' всегда будет возвращать lazyseq. – jmargolisvt

+0

Есть ли у вас какие-либо предположения о том, как я могу решить проблему? Невозможно ли иметь вложенные выражения 'map'? – Henrik

+0

Это как «уведомление» определено ?: '(defn not-ify [expr] (list 'not expr))' – Thumbnail

ответ

2

Короткий ответ заключается в использовании 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))))))) 

Для форм и выражений, нет существенной разницы между списком или последовательности. Кроме того, если вы действительно хотели сохранить listiness, вам просто нужно быть немного более тщательно в преобразовании последовательности в списках :)

+0

Спасибо! Это действительно помогло подтолкнуть меня к холму. Ваш пример выглядит отлично, к сожалению, я действительно не очень хорошо знаю Clojure; Просто случается, что мой дискретный курс математики имеет компьютерные задания в Clojure, не намного больше, чем руководство по основам, поэтому половина вашего кода выглядит как тарабарщина. :П – Henrik

1

Для чего это стоит, ...

Во-первых, давайте определим, что логический 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, я играл с and и or и одноруких if, чтобы избежать повторения ничегонеделания случай, который весит вниз текст OP в.

Как это относится к @TimothyPratley's answer?

  • я бесстыдно украл его замечание об использовании seq? вместо list?.
  • Он выдает исключение из непризнанных операций. Шахта только делает их nil.
  • Я рассмотрел and и or s в одном корпусе.
Смежные вопросы