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}