balanced_direction/
ternary.rs

1use crate::Balance;
2use balanced_ternary::Digit;
3use core::ops::{BitAnd, BitOr, BitXor};
4
5impl BitAnd for Balance {
6    type Output = Self;
7    fn bitand(self, rhs: Self) -> Self::Output {
8        let (x1, y1) = self.to_ternary_pair();
9        let (x2, y2) = rhs.to_ternary_pair();
10        Balance::from_ternary_pair(x1 & x2, y1 & y2)
11    }
12}
13
14impl BitOr for Balance {
15    type Output = Self;
16    fn bitor(self, rhs: Self) -> Self::Output {
17        let (x1, y1) = self.to_ternary_pair();
18        let (x2, y2) = rhs.to_ternary_pair();
19        Balance::from_ternary_pair(x1 | x2, y1 | y2)
20    }
21}
22
23impl BitXor for Balance {
24    type Output = Self;
25    fn bitxor(self, rhs: Self) -> Self::Output {
26        let (x1, y1) = self.to_ternary_pair();
27        let (x2, y2) = rhs.to_ternary_pair();
28        Balance::from_ternary_pair(x1 ^ x2, y1 ^ y2)
29    }
30}
31
32impl Balance {
33    /// Converts the `Balance` position into a pair of ternary digits.
34    ///
35    /// # Returns
36    ///
37    /// A tuple containing two `Digit` values. The first element represents
38    /// the x-coordinate and the second represents the y-coordinate of the `Balance`
39    /// position in the ternary numeral system.
40    ///
41    /// The `Digit` values can range from `Neg` (-1), `Zero` (0), to `Pos` (1),
42    /// matching the 3x3 balanced grid's coordinate representation.
43    ///
44    /// # Examples
45    ///
46    /// ```
47    /// use balanced_direction::Balance;
48    /// use balanced_ternary::Digit;
49    ///
50    /// let balance = Balance::Top;
51    /// assert_eq!(balance.to_ternary_pair(), (Digit::Zero, Digit::Neg));
52    ///
53    /// let balance = Balance::Right;
54    /// assert_eq!(balance.to_ternary_pair(), (Digit::Pos, Digit::Zero));
55    /// ```
56    pub const fn to_ternary_pair(self) -> (Digit, Digit) {
57        (Digit::from_i8(self.x()), Digit::from_i8(self.y()))
58    }
59
60    /// Creates a `Balance` instance from a pair of ternary digits.
61    ///
62    /// # Arguments
63    ///
64    /// * `a` - A `Digit` representing the x-coordinate in the ternary numeral system.
65    /// * `b` - A `Digit` representing the y-coordinate in the ternary numeral system.
66    ///
67    /// # Returns
68    ///
69    /// A new `Balance` instance corresponding to the provided ternary coordinates.
70    ///
71    /// The values of `a` and `b` should be valid ternary digits within the range of:
72    /// - `Neg` (-1), `Zero` (0), and `Pos` (1).
73    ///
74    /// This allows for mapping coordinates within the 3x3 grid system used by the `Balance` enum, ensuring
75    /// that any valid pair of ternary digits maps directly to a specific `Balance` position.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// use balanced_direction::Balance;
81    /// use balanced_ternary::Digit;
82    ///
83    /// let balance = Balance::from_ternary_pair(Digit::Zero, Digit::Neg);
84    /// assert_eq!(balance, Balance::Top);
85    ///
86    /// let balance = Balance::from_ternary_pair(Digit::Pos, Digit::Zero);
87    /// assert_eq!(balance, Balance::Right);
88    /// ```
89    pub const fn from_ternary_pair(a: Digit, b: Digit) -> Self {
90        Self::from_vector(a.to_i8(), b.to_i8())
91    }
92
93    /// (logic) Checks if the current logical state is `Balance::BottomRight` (True, True).
94    pub const fn is_true(self) -> bool {
95        matches!(self, Balance::BottomRight)
96    }
97
98    /// (logic) Checks if the current logical state includes `Balance::Bottom` or `Balance::Right`.
99    ///
100    /// * [Balance::TopRight] (True, False)
101    /// * [Balance::Right] (True, Unknown)
102    /// * [Balance::BottomLeft] (False, True)
103    /// * [Balance::Bottom] (Unknown, True)
104    /// * [Balance::BottomRight] (True, True)
105    pub const fn has_true(self) -> bool {
106        self.x() == 1 || self.y() == 1
107    }
108
109    /// (logic) Checks if the current logical state is contradictory, representing opposing truths (`TopRight` or `BottomLeft`).
110    ///
111    /// * [Balance::TopRight] (True, False)
112    /// * [Balance::BottomLeft] (False, True)
113    pub const fn is_contradictory(self) -> bool {
114        matches!(self, Balance::TopRight | Balance::BottomLeft)
115    }
116
117    /// (logic) Checks whether the current logical state has no certain value but is not contradictory.
118    ///
119    /// * [Balance::Top] (Unknown, False)
120    /// * [Balance::Left] (False, Unknown)
121    /// * [Balance::Center] (Unknown, Unknown)
122    /// * [Balance::Right] (True, Unknown)
123    /// * [Balance::Bottom] (Unknown, True)
124    pub const fn has_unknown(self) -> bool {
125        // = self.is_orthogonal().
126        self.x() == 0 || self.y() == 0
127    }
128
129    /// (logic) Checks whether the current logical state is uncertain in terms of logical balance.
130    ///
131    /// Equals:
132    /// * `is_contradictory() || has_unknown()`,
133    /// * or `!is_certain()`.
134    ///
135    /// These cases return `true`:
136    /// * [Balance::TopRight] (True, False)
137    /// * [Balance::BottomLeft] (False, True)
138    /// * [Balance::Top] (Unknown, False)
139    /// * [Balance::Left] (False, Unknown)
140    /// * [Balance::Center] (Unknown, Unknown)
141    /// * [Balance::Right] (True, Unknown)
142    /// * [Balance::Bottom] (Unknown, True)
143    pub const fn is_uncertain(self) -> bool {
144        !self.is_certain()
145    }
146
147    /// (logic) Returns whether the current logical state represents a certain state in logical balance (one of `is_true()` or `is_false()` is true).
148    ///
149    /// * [Balance::TopLeft] (False, False)
150    /// * [Balance::BottomRight] (True, True)
151    pub const fn is_certain(self) -> bool {
152        matches!(self, Balance::BottomRight | Balance::TopLeft)
153    }
154
155    /// (logic) Determines whether the current logical state includes the `Balance::Top` or `Balance::Left` variant.
156    ///
157    /// * [Balance::TopLeft] (False, False)
158    /// * [Balance::Top] (Unknown, False)
159    /// * [Balance::TopRight] (True, False)
160    /// * [Balance::Left] (False, Unknown)
161    /// * [Balance::BottomLeft] (False, True)
162    pub const fn has_false(self) -> bool {
163        self.x() == -1 || self.y() == -1
164    }
165
166    /// (logic) Checks if the current logical state is `Balance::TopLeft` (False, False).
167    pub const fn is_false(self) -> bool {
168        matches!(self, Balance::TopLeft)
169    }
170
171    /// Converts the `Balance` logical state into a boolean representation.
172    ///
173    /// # Returns
174    ///
175    /// A `bool` value that represents the certainty of the `Balance` logical state.
176    /// - Returns `true` if the position is logically `true` (e.g., `Balance::BottomRight`).
177    /// - Returns `false` if the position is logically `false` (e.g., `Balance::TopLeft`).
178    ///
179    /// # Panics
180    ///
181    /// Panics if the `Balance` logical state is uncertain. Uncertain states include cases
182    /// where the position cannot be determined or does not logically map to `true` or `false`.
183    ///
184    /// > Use [Balance::is_certain] to check certainty of the `Balance` logical state.
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use balanced_direction::Balance;
190    ///
191    /// let balance = Balance::BottomRight;
192    /// assert_eq!(balance.to_bool(), true);
193    ///
194    /// let balance = Balance::TopLeft;
195    /// assert_eq!(balance.to_bool(), false);
196    ///
197    /// let balance = Balance::Center;
198    /// // This will panic because `Balance::Center` is an uncertain logical state.
199    /// // balance.to_bool();
200    /// ```
201    pub const fn to_bool(self) -> bool {
202        self.is_true() || {
203            if self.is_false() {
204                false
205            } else {
206                panic!("Cannot convert an uncertain Balance to a boolean value.")
207            }
208        }
209    }
210
211    /// Converts the x-coordinate of the `Balance` position into a boolean representation.
212    ///
213    /// # Returns
214    ///
215    /// A `bool` value representing the logical state of the x-coordinate:
216    /// - Returns `true` if the x-coordinate is logically `true` (1).
217    /// - Returns `false` if the x-coordinate is logically `false` (-1).
218    ///
219    /// # Panics
220    ///
221    /// Panics if the x-coordinate is unknown (i.e., `0`), as it cannot be converted
222    /// into a boolean value.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// use balanced_direction::Balance;
228    ///
229    /// let balance = Balance::Right;
230    /// assert_eq!(balance.x_to_bool(), true);
231    ///
232    /// let balance = Balance::Left;
233    /// assert_eq!(balance.x_to_bool(), false);
234    ///
235    /// let balance = Balance::Center;
236    /// // This will panic because the x-coordinate is unknown.
237    /// // balance.x_to_bool();
238    /// ```
239    pub const fn x_to_bool(self) -> bool {
240        self.x() == 1 || {
241            if self.x() == -1 {
242                false
243            } else {
244                panic!("Cannot convert an unknown-x Balance to a boolean value.")
245            }
246        }
247    }
248
249    /// Converts the y-coordinate of the `Balance` position into a boolean representation.
250    ///
251    /// # Returns
252    ///
253    /// A `bool` value representing the logical state of the y-coordinate:
254    /// - Returns `true` if the y-coordinate is logically `true` (1).
255    /// - Returns `false` if the y-coordinate is logically `false` (-1).
256    ///
257    /// # Panics
258    ///
259    /// Panics if the y-coordinate is unknown (i.e., `0`), as it cannot be converted
260    /// into a boolean value.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use balanced_direction::Balance;
266    ///
267    /// let balance = Balance::Top;
268    /// assert_eq!(balance.y_to_bool(), false);
269    ///
270    /// let balance = Balance::Bottom;
271    /// assert_eq!(balance.y_to_bool(), true);
272    ///
273    /// let balance = Balance::Center;
274    /// // This will panic because the y-coordinate is unknown.
275    /// // balance.y_to_bool();
276    /// ```
277    pub const fn y_to_bool(self) -> bool {
278        self.y() == 1 || {
279            if self.y() == -1 {
280                false
281            } else {
282                panic!("Cannot convert an unknown-y Balance to a boolean value.")
283            }
284        }
285    }
286
287    /// Applies [Digit::possibly] on `x` and `y`.
288    pub const fn possibly(self) -> Self {
289        let (x, y) = self.to_ternary_pair();
290        Self::from_ternary_pair(x.possibly(), y.possibly())
291    }
292    /// Applies [Digit::necessary] on `x` and `y`.
293    pub const fn necessary(self) -> Self {
294        let (x, y) = self.to_ternary_pair();
295        Self::from_ternary_pair(x.necessary(), y.necessary())
296    }
297    /// Applies [Digit::contingently] on `x` and `y`.
298    pub const fn contingently(self) -> Self {
299        let (x, y) = self.to_ternary_pair();
300        Self::from_ternary_pair(x.contingently(), y.contingently())
301    }
302    /// Applies [Digit::absolute_positive] on `x` and `y`.
303    pub const fn absolute_positive(self) -> Self {
304        let (x, y) = self.to_ternary_pair();
305        Self::from_ternary_pair(x.absolute_positive(), y.absolute_positive())
306    }
307    /// Applies [Digit::positive] on `x` and `y`.
308    pub const fn positive(self) -> Self {
309        let (x, y) = self.to_ternary_pair();
310        Self::from_ternary_pair(x.positive(), y.positive())
311    }
312    /// Applies [Digit::not_negative] on `x` and `y`.
313    pub const fn not_negative(self) -> Self {
314        let (x, y) = self.to_ternary_pair();
315        Self::from_ternary_pair(x.not_negative(), y.not_negative())
316    }
317    /// Applies [Digit::not_positive] on `x` and `y`.
318    pub const fn not_positive(self) -> Self {
319        let (x, y) = self.to_ternary_pair();
320        Self::from_ternary_pair(x.not_positive(), y.not_positive())
321    }
322    /// Applies [Digit::negative] on `x` and `y`.
323    pub const fn negative(self) -> Self {
324        let (x, y) = self.to_ternary_pair();
325        Self::from_ternary_pair(x.negative(), y.negative())
326    }
327    /// Applies [Digit::absolute_negative] on `x` and `y`.
328    pub const fn absolute_negative(self) -> Self {
329        let (x, y) = self.to_ternary_pair();
330        Self::from_ternary_pair(x.absolute_negative(), y.absolute_negative())
331    }
332    /// Applies [Digit::ht_not] on `x` and `y`.
333    pub const fn ht_not(self) -> Self {
334        let (x, y) = self.to_ternary_pair();
335        Self::from_ternary_pair(x.ht_not(), y.ht_not())
336    }
337    /// Applies [Digit::post] on `x` and `y`.
338    pub const fn post(self) -> Self {
339        let (x, y) = self.to_ternary_pair();
340        Self::from_ternary_pair(x.post(), y.post())
341    }
342    /// Applies [Digit::pre] on `x` and `y`.
343    pub const fn pre(self) -> Self {
344        let (x, y) = self.to_ternary_pair();
345        Self::from_ternary_pair(x.pre(), y.pre())
346    }
347    /// Applies [Digit::k3_imply] on `x` and `y`.
348    pub const fn k3_imply(self, other: Self) -> Self {
349        let (x1, y1) = self.to_ternary_pair();
350        let (x2, y2) = other.to_ternary_pair();
351        Self::from_ternary_pair(x1.k3_imply(x2), y1.k3_imply(y2))
352    }
353    /// Applies [Digit::k3_equiv] on `x` and `y`.
354    ///
355    /// Equivalent to `self * other`.
356    pub const fn k3_equiv(self, other: Self) -> Self {
357        let (x1, y1) = self.to_ternary_pair();
358        let (x2, y2) = other.to_ternary_pair();
359        Self::from_ternary_pair(x1.k3_equiv(x2), y1.k3_equiv(y2))
360    }
361    /// Applies [Digit::ht_imply] on `x` and `y`.
362    pub const fn ht_imply(self, other: Self) -> Self {
363        let (x1, y1) = self.to_ternary_pair();
364        let (x2, y2) = other.to_ternary_pair();
365        Self::from_ternary_pair(x1.ht_imply(x2), y1.ht_imply(y2))
366    }
367
368    /// Applies a transformation function `x` and another on `y` coordinates.
369    ///
370    /// This function allows you to provide two different transformation functions,
371    /// one for `x` (`op_x`) and another for `y` (`op_y`). These functions
372    /// process the coordinates independently, and the resulting transformed
373    /// `x` and `y` coordinates are combined into a new `Balance`.
374    ///
375    /// # Parameters
376    ///
377    /// - `op_x`: A function that transforms the `x` coordinate.
378    /// - `op_y`: A function that transforms the `y` coordinate.
379    ///
380    /// # Returns
381    ///
382    /// A new `Balance` object with the transformed `x` and `y` coordinates.
383    ///
384    /// # Examples
385    ///
386    /// ```
387    /// use balanced_ternary::Digit;
388    /// use balanced_direction::Balance;
389    ///
390    /// let balance = Balance::Center;
391    /// let transformed = balance.apply(Digit::not_negative, Digit::not_positive);
392    /// assert_eq!(transformed, Balance::TopRight);
393    /// ```
394    pub fn apply<FX, FY>(self, op_x: FX, op_y: FY) -> Self
395    where
396        FX: Fn(Digit) -> Digit,
397        FY: Fn(Digit) -> Digit,
398    {
399        let (x, y) = self.to_ternary_pair();
400        Self::from_ternary_pair(op_x(x), op_y(y))
401    }
402
403    /// Applies two transformation functions - one for `x` and one for `y` - on a `Balance` instance along with another `Balance`.
404    ///
405    /// This function takes two coordinate transformation functions (`op_x` and `op_y`) as well as another `Balance` instance (`other`).
406    /// It applies `op_x` to the `x` coordinates of both balances, and `op_y` to the `y` coordinates of both balances, combining the results into a new `Balance`.
407    ///
408    /// # Parameters
409    ///
410    /// - `op_x`: A function that transforms the `x` coordinates of both balances.
411    /// - `op_y`: A function that transforms the `y` coordinates of both balances.
412    /// - `other`: The second `Balance` object to use for coordinate transformations.
413    ///
414    /// # Returns
415    ///
416    /// A new `Balance` object with transformed `x` and `y` based on the provided functions and the two input balances.
417    ///
418    /// # Examples
419    ///
420    /// ```
421    /// use balanced_ternary::Digit;
422    /// use balanced_direction::Balance;
423    ///
424    /// let balance1 = Balance::TopRight;
425    /// let balance2 = Balance::BottomLeft;
426    ///
427    /// let result = balance1.apply_with(
428    ///     |x1, x2| x1.k3_equiv(x2),
429    ///     |y1, y2| y1.k3_imply(y2),
430    ///     balance2
431    /// );
432    ///
433    /// assert_eq!(result, Balance::BottomLeft);
434    /// ```
435    pub fn apply_with<FX, FY>(self, op_x: FX, op_y: FY, other: Self) -> Self
436    where
437        FX: Fn(Digit, Digit) -> Digit,
438        FY: Fn(Digit, Digit) -> Digit,
439    {
440        let (x1, y1) = self.to_ternary_pair();
441        let (x2, y2) = other.to_ternary_pair();
442        Self::from_ternary_pair(op_x(x1, x2), op_y(y1, y2))
443    }
444    /// Applies the given transformation on both `x` and `y`.
445    /// 
446    /// See [Balance::apply].
447    pub fn apply_both<F>(self, op: F) -> Self
448    where
449        F: Fn(Digit) -> Digit + Clone,
450    {
451        self.apply(op.clone(), op)
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458    use crate::tests::assert_eq_array;
459
460    const BALANCES: [Balance; 9] = [
461        Balance::TopLeft,
462        Balance::Top,
463        Balance::TopRight,
464        Balance::Left,
465        Balance::Center,
466        Balance::Right,
467        Balance::BottomLeft,
468        Balance::Bottom,
469        Balance::BottomRight,
470    ];
471
472    #[test]
473    fn test_possibly() {
474        let results = BALANCES.map(Balance::possibly);
475        assert_eq_array(
476            results,
477            [
478                Balance::TopLeft,
479                Balance::TopRight,
480                Balance::TopRight,
481                Balance::BottomLeft,
482                Balance::BottomRight,
483                Balance::BottomRight,
484                Balance::BottomLeft,
485                Balance::BottomRight,
486                Balance::BottomRight,
487            ],
488        );
489    }
490
491    #[test]
492    fn test_necessary() {
493        let results = BALANCES.map(Balance::necessary);
494        assert_eq_array(
495            results,
496            [
497                Balance::TopLeft,
498                Balance::TopLeft,
499                Balance::TopRight,
500                Balance::TopLeft,
501                Balance::TopLeft,
502                Balance::TopRight,
503                Balance::BottomLeft,
504                Balance::BottomLeft,
505                Balance::BottomRight,
506            ],
507        );
508    }
509
510    #[test]
511    fn test_contingently() {
512        let results = BALANCES.map(Balance::contingently);
513        assert_eq_array(
514            results,
515            [
516                Balance::TopLeft,
517                Balance::TopRight,
518                Balance::TopLeft,
519                Balance::BottomLeft,
520                Balance::BottomRight,
521                Balance::BottomLeft,
522                Balance::TopLeft,
523                Balance::TopRight,
524                Balance::TopLeft,
525            ],
526        );
527    }
528
529    #[test]
530    fn test_absolute_positive() {
531        assert_eq_array(
532            BALANCES.map(Balance::absolute_positive),
533            [
534                Balance::BottomRight,
535                Balance::Bottom,
536                Balance::BottomRight,
537                Balance::Right,
538                Balance::Center,
539                Balance::Right,
540                Balance::BottomRight,
541                Balance::Bottom,
542                Balance::BottomRight,
543            ],
544        );
545    }
546
547    #[test]
548    fn test_positive() {
549        assert_eq_array(
550            BALANCES.map(Balance::positive),
551            [
552                Balance::Center,
553                Balance::Center,
554                Balance::Right,
555                Balance::Center,
556                Balance::Center,
557                Balance::Right,
558                Balance::Bottom,
559                Balance::Bottom,
560                Balance::BottomRight,
561            ],
562        );
563    }
564
565    #[test]
566    fn test_not_negative() {
567        assert_eq_array(
568            BALANCES.map(Balance::not_negative),
569            [
570                Balance::Center,
571                Balance::Right,
572                Balance::Right,
573                Balance::Bottom,
574                Balance::BottomRight,
575                Balance::BottomRight,
576                Balance::Bottom,
577                Balance::BottomRight,
578                Balance::BottomRight,
579            ],
580        );
581    }
582
583    #[test]
584    fn test_not_positive() {
585        assert_eq_array(
586            BALANCES.map(Balance::not_positive),
587            [
588                Balance::TopLeft,
589                Balance::TopLeft,
590                Balance::Top,
591                Balance::TopLeft,
592                Balance::TopLeft,
593                Balance::Top,
594                Balance::Left,
595                Balance::Left,
596                Balance::Center,
597            ],
598        );
599    }
600
601    #[test]
602    fn test_negative() {
603        assert_eq_array(
604            BALANCES.map(Balance::negative),
605            [
606                Balance::TopLeft,
607                Balance::Top,
608                Balance::Top,
609                Balance::Left,
610                Balance::Center,
611                Balance::Center,
612                Balance::Left,
613                Balance::Center,
614                Balance::Center,
615            ],
616        );
617    }
618
619    #[test]
620    fn test_absolute_negative() {
621        assert_eq_array(
622            BALANCES.map(Balance::absolute_negative),
623            [
624                Balance::TopLeft,
625                Balance::Top,
626                Balance::TopLeft,
627                Balance::Left,
628                Balance::Center,
629                Balance::Left,
630                Balance::TopLeft,
631                Balance::Top,
632                Balance::TopLeft,
633            ],
634        );
635    }
636
637    #[test]
638    fn test_ht_not() {
639        assert_eq_array(
640            BALANCES.map(Balance::ht_not),
641            [
642                Balance::BottomRight,
643                Balance::BottomLeft,
644                Balance::BottomLeft,
645                Balance::TopRight,
646                Balance::TopLeft,
647                Balance::TopLeft,
648                Balance::TopRight,
649                Balance::TopLeft,
650                Balance::TopLeft,
651            ],
652        );
653    }
654
655    #[test]
656    fn test_post() {
657        assert_eq_array(
658            BALANCES.map(Balance::post),
659            [
660                Balance::Center,
661                Balance::Right,
662                Balance::Left,
663                Balance::Bottom,
664                Balance::BottomRight,
665                Balance::BottomLeft,
666                Balance::Top,
667                Balance::TopRight,
668                Balance::TopLeft,
669            ],
670        );
671    }
672
673    #[test]
674    fn test_pre() {
675        assert_eq_array(
676            BALANCES.map(Balance::pre),
677            [
678                Balance::BottomRight,
679                Balance::BottomLeft,
680                Balance::Bottom,
681                Balance::TopRight,
682                Balance::TopLeft,
683                Balance::Top,
684                Balance::Right,
685                Balance::Left,
686                Balance::Center,
687            ],
688        );
689    }
690}