Структура данных для логических выражений в Haskell

Я пытаюсь создать структуру данных для работы с логическими выражениями. На первый взгляд, логические выражения выглядят как Trees, поэтому кажется разумным составить его из деревьев:

data Property a = And (Property a) (Property a) |
                 Or  (Property a) (Property a) |
                 Not (Property a) | Property a   deriving (Show,Eq)

но это не очень хорошая идея, потому что на самом деле нет никакой разницы между левой и правой ветвями моего дерева, поскольку A && B == B && A

Ну, может быть List еще лучше?

data Property a = Empty | And a (Property a) | Or a (Property a) | Not a (Property a)  deriving Show

но в этом случае я не могу сформировать "логическое дерево", только " логический список '. Поэтому мне нужна структура данных, похожая на Tree но без левого и правого "ветвления".

3 ответов


мы собираемся следовать превосходному предложению Даниэля Вагнера и использовать "наивное представление (ваше первое), а также функцию, которая выбирает одну из известных нормальных форм". Мы собираемся использовать алгебраическая нормальная форма по двум причинам. Главная причина заключается в том, что алгебраическая нормальная форма не требует перечисления всех возможных значений переменной, которые хранятся в Property. Алгебраическая нормальная форма также довольно проста.

Алгебраические Нормально Форма

мы представим алгебраическую нормальную форму с newtype ANF a = ANF [[a]] это не экспортирует его конструктор. Каждый внутренний список является соединением (и-ing) всех его предикатов; если внутренний список пуст, он всегда истинен. Внешний список-это исключение или исключение всех его предикатов; если он пуст, он ложен.

module Logic (
    ANF (getANF),
    anf,
    ...
)

newtype ANF a = ANF {getANF :: [[a]]}
  deriving (Eq, Ord, Show)

мы приложим усилия, чтобы любой ANF мы построим будет каноническим. Мы будем строить ANFs, чтобы внутренний список всегда сортировался и уникальный. Внешний список всегда будет отсортирован. Если во внешнем списке есть два (или четное количество) одинаковых элемента, мы удалим их оба. Если во внешнем списке есть нечетное количество одинаковых элементов, мы удалим все, кроме одного.

с помощью функции Data.List.Ordered на data-ordlist пакет, мы можем очистить список списков, представляющих xor-ing союзов в ANF списки сортировки и дубликаты удалены.

import qualified Data.List.Ordered as Ordered

anf :: Ord a => [[a]] -> ANF a
anf = ANF . nubPairs . map Ordered.nubSort

nubPairs :: Ord a => [a] -> [a]
nubPairs = removePairs . Ordered.sort
    where
        removePairs (x:y:zs)
            | x == y    = removePairs zs
            | otherwise = x:removePairs (y:zs)
        removePairs xs = xs

логические выражения образуют булевой алгебры, которая является ограниченной решеткой с дополнительным распределительным законом и дополнением (отрицанием). Использование существующих BoundedLattice класс решетки пакета, мы можем определить класс BooleanAlgebras

import Algebra.Lattice

class (BoundedLattice a) => BooleanAlgebra a where
    complement :: a -> a
    -- Additional Laws:
    -- a `join` complement a == top
    -- a `meet` complement a == bottom
    -- a `join` (b `meet` c) == (a `join` b) `meet` (a `join` c)
    -- a `meet` (b `join` c) == (a `meet` b) `join` (a `meet` c)

ANF aS форма a BooleanAlgebra, когда a есть Ord экземпляр, чтобы мы могли сохранить ANF в канонической форме.

на meet логической булевой алгебры and. Логический and распространяет через xor. Мы воспользуемся тем, что внутренние списки уже отсортированы, чтобы быстро объединить их вместе.

instance (Ord a) => MeetSemiLattice (ANF a) where
    ANF xs `meet` ANF ys = ANF (nubPairs [Ordered.union x y | x <- xs, y <- ys])

используя законы де Моргана, the join или логический or может быть написано в терминах meet или логический and.

instance (Ord a) => JoinSemiLattice (ANF a) where
    xs `join` ys = complement (complement xs `meet` complement ys)

на top решетки это всегда правда.

instance (Ord a) => BoundedMeetSemiLattice (ANF a) where
    top = ANF [[]]

на bottom решетки всегда ложно.

instance (Ord a) => BoundedJoinSemiLattice (ANF a) where
    bottom = ANF []

логическое and и логично or удовлетворяют закону поглощения решетки,a join (a meet b) == a meet (a join b) == a.

instance (Ord a) => Lattice (ANF a)
instance (Ord a) => BoundedLattice (ANF a)

наконец-то complement операция-это отрицание, которое может быть выполнено xoring по true.

instance (Ord a) => BooleanAlgebra (ANF a) where
    complement (ANF ([]:xs)) = ANF xs
    complement (ANF xs)      = ANF ([]:xs)

вместе с Pointed можно использовать BooleanAlgebra чтобы определить класс вещей, содержащих логические выражения, Logical.

import Data.Pointed

class Logical f where
    toLogic :: (Pointed g, BooleanAlgebra (g a)) => f a -> g a

алгебраическая нормальная форма содержит логическое выражение.

xor :: BooleanAlgebra a => a -> a -> a
p `xor` q = (p `join` q) `meet` complement (p `meet` q)

instance Logical ANF where
    toLogic = foldr xor bottom . map (foldr meet top . map point) . getANF

алгебраическая нормальная форма тоже Pointed, и таким образом может быть преобразован в использование toLogic.

instance Pointed ANF where
    point x = ANF [[x]]

toANF :: (Logical f, Ord a) => f a -> ANF a
toANF = toLogic

мы можем проверить, если что-нибудь Logical логически эквивалентен, сравнивая, чтобы увидеть, является ли он структурно эквивалентным при преобразовании в каноническую алгебраическую нормальную форму.

equivalent :: (Logical f, Logical g, Ord a) => f a -> g a -> Bool
equivalent a b = toANF a == toANF b

преобразование свойства в ANF

логические выражения должны образовывать булеву алгебру, которая является ограниченной решеткой с дополнительным распределительным законом и дополнением (отрицанием). Сделать Property более близко параллельная булева алгебра, нам нужно добавить еще два элемента для top и bottom границы решетки. top всегда True и bottom всегда False. Я собираюсь назвать это Trivial для свойств, которые всегда True и Impossible для свойств, которые всегда False.

data Property a
    = And (Property a) (Property a)
    | Or  (Property a) (Property a)
    | Not (Property a)
    | Property a
    | Trivial
    | Impossible
  deriving (Eq, Ord, Read, Show)

Property - это абстрактное синтаксическое дерево для свойства. Его производные Eq и Ord экземпляры всего структурного равенства.

A Property is Logical, и мы можем преобразовать ее в любой Pointed BooleanAlgebra.

instance Logical Property where
    toLogic (And a b)    = (toLogic a) `meet` (toLogic b)
    toLogic (Or  a b)    = (toLogic a) `join` (toLogic b)
    toLogic (Not a)      = complement (toLogic a)
    toLogic (Property a) = point a
    toLogic Trivial      = top
    toLogic Impossible   = bottom

используя equivalent из предыдущего раздела и наши Logical экземпляр, мы можем проверить, эквивалентны ли два свойства для некоторых примеров.

runExample :: (Ord a, Show a) => Property a -> Property a -> IO ()
runExample p q = 
    if p `equivalent` q
    then putStrLn (show p ++ " == " ++ show q)
    else putStrLn (show p ++ " /= " ++ show q)

main = do
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a')
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a' `meet` point 'a')
    runExample (point 'a' `meet` point 'b') (point 'b' `meet` point 'a' `join` point 'a')
    runExample (point 'a' `join` complement (point 'a')) (top)
    runExample (point 'a' `meet` complement (point 'a')) (bottom)

это производит следующий вывод.

And (Property 'a') (Property 'b') == And (Property 'b') (Property 'a')
And (Property 'a') (Property 'b') == And (And (Property 'b') (Property 'a')) (Pr
operty 'a')
And (Property 'a') (Property 'b') /= Or (And (Property 'b') (Property 'a')) (Pro
perty 'a')
Or (Property 'a') (Not (Property 'a')) == Trivial
And (Property 'a') (Not (Property 'a')) == Impossible

чтобы использовать эти примеры как написано, нам тоже нужны BooleanAlgebra и Pointed экземпляров Property. Отношение эквивалентности для BooleanAlgebra законы-это эквивалентные интерпретации, а не структурное равенство Property.

instance MeetSemiLattice (Property a) where
    meet = And

instance BoundedMeetSemiLattice (Property a) where
    top = Trivial

instance JoinSemiLattice (Property a) where
    join = Or

instance BoundedJoinSemiLattice (Property a) where
    bottom = Impossible

instance Lattice (Property a)
instance BoundedLattice (Property a)

instance BooleanAlgebra (Property a) where
    complement = Not

instance Pointed Property where
    point = Property

доказательство

каждая Булева функция и, следовательно, каждая конечная Property, имеет уникальный представительство в ANF. The BooleanAlgebra и Pointed экземпляров ANF показала, что все Logical a, и, таким образом, каждая конечная Property a и Булева функция, индексированная a, имеет представительства в ANF a. Пусть k быть числом жителей a. Есть 2^(2^k) возможные булевы функции k логические переменные. Каждый внутренний список ANF набор a s; есть 2^k возможные наборы as и таким образом 2^k возможные внутренние списки. Внешний список ANF - это набор внутренних списков; каждый внутренний список может появиться не более одного раза во внешнем списке. Есть 2^(2^k) возможно ANF as. Поскольку каждая Булева функция имеет представление в ANF и есть только как можно больше жителей ANF поскольку существуют булевы функции, каждая Булева функция имеет уникальный представительство в ANF.

отказ от ответственности

на Ord экземпляр ANF является структурным порядком и, кроме равенства, не имеет ничего общего с частичными порядками, индуцированными решеточной структурой.

алгебраическая нормальная форма может быть гораздо больше, чем ее ввод. Дизъюнкция списка n переменные имеют размер .5*n*2^n. Например, length . getANF . foldr join bottom . map point $ ['a'..'g'] is 127 и foldr join bottom . map point $ ['a'..'g'] содержит в общей сложности 448 возникновения 7 различные переменные.


Я бы рекомендовал использовать решатель SAT/SMT для проверки эквивалентности. В общем, такие проверки могут быть очень дорогими (NP-complete), и любой вид перевода в нормальные формы может вызвать экспоненциальный взрыв в представлении. Решатели SAT/SMT имеют пользовательские алгоритмы для решения таких проблем, и, возможно, лучше всего использовать такой решатель. Было бы тривиально перевести два экземпляра Property и спросите, одинаковы ли они во всех назначениях переменных. Библиотека SBV (https://hackage.haskell.org/package/sbv) может использоваться для перевода с высокого уровня Haskell. Ответ на следующий вопрос содержит некоторые подробности о том, как это сделать:SAT решение с библиотекой Haskell SBV: как создать предикат из проанализированной строки?


если вы хотите отразить ассоциативность логических связок и (&&) и или, используйте ассоциативную структуру данных, например list:

data Property a = And [Property a] | ...

если вы хотите коммутативность (A && B == B && A), иди с данные.Set.

Я помню эту удобную диаграмму, для каких типов использовать, если вы хотите определенные свойства:

+-----------+-----------+----------+----------
|Associative|Commutative|Idempotent|   type
+-----------+-----------+----------+----------
|(a+b)+c=   |  a+b=b+a  |  a+a=a   |
|    a+(b+c)|           |          |
+-----------+-----------+----------+----------
|    no     |    no     |    no    |binary trees
|    no     |   yes     |    no    | “mobiles”
|   yes     |    no     |    no    |lists (arrays)
|   yes     |   yes     |    no    |multisets (bags)
|   yes     |   yes     |   yes    |sets
+-----------+-----------+----------+----------

страница 51 из лекция Гая Стила.