Структура данных для логических выражений в 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 из лекция Гая Стила.