boolean_expression/
expr.rs

1// boolean_expression: a Rust crate for Boolean expressions and BDDs.
2//
3// Copyright (c) 2016 Chris Fallin <cfallin@c1f.net>. Released under the MIT
4// License.
5//
6
7use std::cmp::Ord;
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::hash::Hash;
11use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
12
13use simplify;
14
15/// An `Expr` is a simple Boolean logic expression. It may contain terminals
16/// (i.e., free variables), constants, and the following fundamental operations:
17/// AND, OR, NOT.
18///
19/// ```
20/// use std::collections::HashMap;
21/// use boolean_expression::Expr;
22///
23/// #[derive(Clone, Hash, Eq, PartialEq, Debug)]
24/// enum RiverCrossing { Chicken, Fox, Grain }
25///
26/// let chicken = Expr::Terminal(RiverCrossing::Chicken);
27/// let fox_and_grain = Expr::Terminal(RiverCrossing::Fox) & Expr::Terminal(RiverCrossing::Grain);
28///
29/// let allowed = (!chicken.clone() & fox_and_grain.clone()) | (chicken & !fox_and_grain);
30/// let items = [
31///    (RiverCrossing::Grain, true),
32///    (RiverCrossing::Fox, true),
33/// ].iter().cloned().collect();
34///
35/// // nobody gets eaten!
36/// assert!(allowed.evaluate(&items));
37/// ```
38#[derive(Clone, Debug, PartialEq, Eq, Hash)]
39pub enum Expr<T>
40where
41    T: Clone + Debug + Eq + Hash,
42{
43    /// A terminal (free variable). This expression node represents a value that
44    /// is not known until evaluation time.
45    Terminal(T),
46    /// A boolean constant: true or false.
47    Const(bool),
48
49    /// The logical complement of the contained expression argument.
50    Not(Box<Expr<T>>),
51
52    /// The logical AND of the two expression arguments.
53    And(Box<Expr<T>>, Box<Expr<T>>),
54
55    /// The logical OR of the two expression arguments.
56    Or(Box<Expr<T>>, Box<Expr<T>>),
57}
58
59impl<T> Expr<T>
60where
61    T: Clone + Debug + Eq + Hash,
62{
63    /// Returns `true` if this `Expr` is a terminal.
64    pub fn is_terminal(&self) -> bool {
65        match self {
66            &Expr::Terminal(_) => true,
67            _ => false,
68        }
69    }
70
71    /// Returns `true` if this `Expr` is a constant.
72    pub fn is_const(&self) -> bool {
73        match self {
74            &Expr::Const(_) => true,
75            _ => false,
76        }
77    }
78
79    /// Returns `true` if this `Expr` is a NOT node.
80    pub fn is_not(&self) -> bool {
81        match self {
82            &Expr::Not(_) => true,
83            _ => false,
84        }
85    }
86
87    /// Returns `true` if this `Expr` is an AND node.
88    pub fn is_and(&self) -> bool {
89        match self {
90            &Expr::And(_, _) => true,
91            _ => false,
92        }
93    }
94
95    /// Returns `true` if this `Expr` is an OR node.
96    pub fn is_or(&self) -> bool {
97        match self {
98            &Expr::Or(_, _) => true,
99            _ => false,
100        }
101    }
102
103    /// Builds a NOT node around an argument, consuming the argument
104    /// expression.
105    pub fn not(e: Expr<T>) -> Expr<T> {
106        Expr::Not(Box::new(e))
107    }
108
109    /// Builds an AND node around two arguments, consuming the argument
110    /// expressions.
111    pub fn and(e1: Expr<T>, e2: Expr<T>) -> Expr<T> {
112        Expr::And(Box::new(e1), Box::new(e2))
113    }
114
115    /// Builds an OR node around two arguments, consuming the argument
116    /// expressions.
117    pub fn or(e1: Expr<T>, e2: Expr<T>) -> Expr<T> {
118        Expr::Or(Box::new(e1), Box::new(e2))
119    }
120
121    pub fn xor(e1: Expr<T>, e2: Expr<T>) -> Expr<T> {
122        let nand = !(e1.clone() & e2.clone());
123        let or = e1 | e2;
124        nand & or
125    }
126
127    /// Evaluates the expression with a particular set of terminal assignments.
128    /// If any terminals are not assigned, they default to `false`.
129    pub fn evaluate(&self, vals: &HashMap<T, bool>) -> bool {
130        match self {
131            &Expr::Terminal(ref t) => *vals.get(t).unwrap_or(&false),
132            &Expr::Const(val) => val,
133            &Expr::And(ref a, ref b) => a.evaluate(vals) && b.evaluate(vals),
134            &Expr::Or(ref a, ref b) => a.evaluate(vals) || b.evaluate(vals),
135            &Expr::Not(ref x) => !x.evaluate(vals),
136        }
137    }
138
139    /// Evaluates the expression using the provided function to map terminals
140    /// (variables) to boolean values. This is a generalization of
141    /// [`Expr::evaluate`], where the variable lookup in a hashmap is replaced
142    /// with an arbitrary computation.
143    ///
144    ///```
145    /// use boolean_expression::Expr;
146    ///
147    /// let expression = Expr::Terminal(10) | Expr::Terminal(3);
148    ///
149    /// // check if the expression satisfies a predicate
150    /// assert!(expression.evaluate_with(|&x| x > 5));
151    /// ```
152    pub fn evaluate_with<F>(&self, f: F) -> bool
153    where
154        F: Fn(&T) -> bool,
155    {
156        self.evaluate_with1(&f)
157    }
158
159    fn evaluate_with1<F>(&self, f: &F) -> bool
160    where
161        F: Fn(&T) -> bool,
162    {
163        match self {
164            Expr::Terminal(t) => f(t),
165            Expr::Const(val) => *val,
166            Expr::And(a, b) => a.evaluate_with1(f) && b.evaluate_with1(f),
167            Expr::Or(a, b) => a.evaluate_with1(f) || b.evaluate_with1(f),
168            Expr::Not(x) => !x.evaluate_with1(f),
169        }
170    }
171
172    /// Simplify an expression in a relatively cheap way using well-known logic
173    /// identities.
174    ///
175    /// This function performs certain reductions using DeMorgan's Law,
176    /// distribution of ANDs over ORs, and certain identities involving
177    /// constants, to simplify an expression. The result will always be in
178    /// sum-of-products form: nodes will always appear in order (from
179    /// expression tree root to leaves) `OR -> AND -> NOT`. In other words,
180    /// `AND` nodes may only have `NOT` nodes (or terminals or constants) as
181    /// children, and `NOT` nodes may only have terminals or constants as
182    /// children.
183    ///
184    /// This function explicitly does *not* perform any sort of minterm reduction.
185    /// The terms may thus be redundant (i.e., `And(a, b)` may appear twice), and
186    /// combinable terms may exist (i.e., `And(a, b)` and `And(a, Not(b))` may
187    /// appear in the `OR`'d list of terms, where these could be combined to
188    /// simply `a` but are not). For example:
189    ///
190    /// ```
191    /// use boolean_expression::Expr;
192    ///
193    /// // This simplifies using DeMorgan's Law:
194    /// let expr = Expr::not(Expr::or(Expr::Terminal(0), Expr::Terminal(1)));
195    /// let simplified = expr.simplify_via_laws();
196    /// assert_eq!(simplified,
197    ///     Expr::and(Expr::not(Expr::Terminal(0)),
198    ///               Expr::not(Expr::Terminal(1))));
199    ///
200    /// // This doesn't simplify, though:
201    /// let expr = Expr::or(
202    ///             Expr::and(Expr::Terminal(0), Expr::Terminal(1)),
203    ///             Expr::and(Expr::Terminal(0), Expr::not(Expr::Terminal(1))));
204    /// let simplified = expr.clone().simplify_via_laws();
205    /// assert_eq!(simplified, expr);
206    /// ```
207    ///
208    /// For better (but more expensive) simplification, see `simplify_via_bdd()`.
209    pub fn simplify_via_laws(self) -> Expr<T> {
210        simplify::simplify_via_laws(self)
211    }
212
213    /// Simplify an expression via a roundtrip through a `BDD`. This procedure
214    /// is more effective than `Expr::simplify_via_laws()`, but more expensive.
215    /// This roundtrip will implicitly simplify an arbitrarily complicated
216    /// function (by construction, as the BDD is built), and then find a
217    /// quasi-minimal set of terms using cubelist-based reduction. For example:
218    ///
219    /// ```
220    /// use boolean_expression::Expr;
221    ///
222    /// // `simplify_via_laws()` cannot combine these terms, but
223    /// // `simplify_via_bdd()` will:
224    /// let expr = Expr::or(
225    ///             Expr::and(Expr::Terminal(0), Expr::Terminal(1)),
226    ///             Expr::and(Expr::Terminal(0), Expr::not(Expr::Terminal(1))));
227    /// let simplified = expr.simplify_via_bdd();
228    /// assert_eq!(simplified, Expr::Terminal(0));
229    /// ```
230    pub fn simplify_via_bdd(self) -> Expr<T> {
231        simplify::simplify_via_bdd(self)
232    }
233
234    /// Map terminal values using the specified mapping function.
235    pub fn map<F, R>(&self, f: F) -> Expr<R>
236    where
237        F: Fn(&T) -> R,
238        R: Clone + Debug + Eq + Hash,
239    {
240        self.map1(&f)
241    }
242
243    fn map1<F, R>(&self, f: &F) -> Expr<R>
244    where
245        F: Fn(&T) -> R,
246        R: Clone + Debug + Eq + Hash,
247    {
248        match self {
249            &Expr::Terminal(ref t) => Expr::Terminal(f(t)),
250            &Expr::Const(val) => Expr::Const(val),
251            &Expr::Not(ref n) => Expr::not(n.map1(f)),
252            &Expr::And(ref a, ref b) => Expr::and(a.map1(f), b.map1(f)),
253            &Expr::Or(ref a, ref b) => Expr::or(a.map1(f), b.map1(f)),
254        }
255    }
256}
257
258impl<T> Not for Expr<T>
259where
260    T: Clone + Debug + Eq + Hash,
261{
262    type Output = Self;
263
264    fn not(self) -> Self::Output {
265        Self::not(self)
266    }
267}
268
269impl<T> BitAnd<Expr<T>> for Expr<T>
270where
271    T: Clone + Debug + Eq + Hash,
272{
273    type Output = Self;
274
275    fn bitand(self, rhs: Expr<T>) -> Self::Output {
276        Self::and(self, rhs)
277    }
278}
279
280impl<T> BitAndAssign<Expr<T>> for Expr<T>
281where
282    T: Clone + Debug + Eq + Hash,
283{
284    fn bitand_assign(&mut self, rhs: Expr<T>) {
285        *self = Self::and(self.clone(), rhs);
286    }
287}
288
289impl<T> BitOr<Expr<T>> for Expr<T>
290where
291    T: Clone + Debug + Eq + Hash,
292{
293    type Output = Self;
294
295    fn bitor(self, rhs: Expr<T>) -> Self::Output {
296        Self::or(self, rhs)
297    }
298}
299
300impl<T> BitOrAssign<Expr<T>> for Expr<T>
301where
302    T: Clone + Debug + Eq + Hash,
303{
304    fn bitor_assign(&mut self, rhs: Expr<T>) {
305        *self = Self::or(self.clone(), rhs);
306    }
307}
308
309impl<T> BitXor<Expr<T>> for Expr<T>
310where
311    T: Clone + Debug + Eq + Hash,
312{
313    type Output = Self;
314
315    fn bitxor(self, rhs: Expr<T>) -> Self::Output {
316        Self::xor(self, rhs)
317    }
318}
319
320impl<T> BitXorAssign<Expr<T>> for Expr<T>
321where
322    T: Clone + Debug + Eq + Hash,
323{
324    fn bitxor_assign(&mut self, rhs: Expr<T>) {
325        *self = Self::xor(self.clone(), rhs);
326    }
327}