balanced_ternary/
lib.rs

1//! A [balanced ternary](https://en.wikipedia.org/wiki/Balanced_ternary) data structure.
2//!
3//! A `Ternary` object in this module represents a number in the balanced ternary numeral system.
4//! Balanced ternary is a non-standard positional numeral system that uses three digits: {-1, 0, +1}
5//! represented here as `Neg` for -1, `Zero` for 0, and `Pos` for +1. It is useful in some domains
6//! of computer science and mathematics due to its arithmetic properties and representation
7//! symmetry.
8//!
9//! # Data Structures
10//!
11//! - **`Digit` Enum**:
12//!     Represents a single digit for balanced ternary values, with possible values:
13//!     - `Neg` for -1
14//!     - `Zero` for 0
15//!     - `Pos` for +1
16
17#![no_std]
18extern crate alloc;
19
20#[cfg(feature = "ternary-string")]
21use alloc::{format, string::String, string::ToString, vec, vec::Vec};
22
23#[cfg(feature = "ternary-string")]
24use core::{
25    fmt::{Display, Formatter},
26    str::FromStr,
27};
28
29/// Provides helper functions for formatting integers in a given radix.
30///
31/// Used internally to convert decimal numbers into their ternary representation.
32/// - `x`: The number to be formatted.
33/// - `radix`: The base of the numeral system.
34///
35/// Returns a string representation of the number in the specified base.
36#[cfg(feature = "ternary-string")]
37fn format_radix(x: i64, radix: u32) -> String {
38    let mut result = vec![];
39    let sign = x.signum();
40    let mut x = x.unsigned_abs();
41    loop {
42        let m = (x % radix as u64) as u32;
43        x /= radix as u64;
44        result.push(core::char::from_digit(m, radix).unwrap());
45        if x == 0 {
46            break;
47        }
48    }
49    format!(
50        "{}{}",
51        if sign == -1 { "-" } else { "" },
52        result.into_iter().rev().collect::<String>()
53    )
54}
55
56mod digit;
57
58pub use crate::digit::{
59    Digit,
60    Digit::{Neg, Pos, Zero},
61};
62
63/// Converts a character into a `Digit`.
64///
65/// # Arguments
66/// * `from` - A single character (`+`, `0`, or `-`).
67/// * **Panics** if the input character is invalid.
68///
69/// # Returns
70/// * A `Digit` enum corresponding to the character.
71///
72/// # Example
73/// ```
74/// use balanced_ternary::{trit, Digit};
75///
76/// let digit = trit('+');
77/// assert_eq!(digit, Digit::Pos);
78/// ```
79pub const fn trit(from: char) -> Digit {
80    Digit::from_char(from)
81}
82
83/// Converts a string representation of a balanced ternary number into a `Ternary` object.
84///
85/// This function is a convenient shorthand for creating `Ternary` numbers
86/// from string representations. The input string must consist of balanced
87/// ternary characters: `+`, `0`, and `-`.
88///
89/// # Arguments
90///
91/// * `from` - A string slice representing the balanced ternary number.
92/// * **Panics** if an input character is invalid.
93///
94/// # Returns
95///
96/// A `Ternary` object created from the provided string representation.
97///
98/// # Example
99/// ```
100/// use balanced_ternary::{ter, Ternary};
101///
102/// let ternary = ter("+-0+");
103/// assert_eq!(ternary.to_string(), "+-0+");
104/// ```
105#[cfg(feature = "ternary-string")]
106pub fn ter(from: &str) -> Ternary {
107    Ternary::parse(from)
108}
109
110#[cfg(feature = "tryte")]
111/// Creates a `Tryte` object from a string representation of a balanced ternary number.
112/// It contains approximately 9.5 bits of information.
113///
114/// This function first converts the input string representation into a `Ternary` object
115/// using the `ter` function, and then constructs a `Tryte` from that `Ternary`.
116///
117/// # Panics
118///
119/// This function panics if the `Ternary` contains more than 6 digits or if an input character is invalid.
120///
121/// # Arguments
122///
123/// * `from` - A string slice representing the balanced ternary number. It must contain
124///   valid balanced ternary characters (`+`, `0`, or `-`) only.
125/// * Panics if an input character is invalid.
126///
127/// # Returns
128///
129/// A `Tryte` object constructed from the provided balanced ternary string.
130///
131/// # Example
132/// ```
133/// use balanced_ternary::{tryte, Tryte};
134///
135/// let tryte_value = tryte("+0+0");
136/// assert_eq!(tryte_value.to_string(), "00+0+0");
137/// ```
138pub fn tryte(from: &str) -> Tryte {
139    Tryte::from_ternary(&ter(from))
140}
141
142/// Represents a balanced ternary number using a sequence of `Digit`s.
143///
144/// Provides functions for creating, parsing, converting, and manipulating balanced ternary numbers.
145#[derive(Debug, Clone, PartialEq, Eq, Hash)]
146#[cfg(feature = "ternary-string")]
147pub struct Ternary {
148    digits: Vec<Digit>,
149}
150
151#[cfg(feature = "ternary-string")]
152impl Ternary {
153    /// Creates a new balanced ternary number from a vector of `Digit`s.
154    pub fn new(digits: Vec<Digit>) -> Ternary {
155        Ternary { digits }
156    }
157
158    /// Returns the number of digits (length) of the balanced ternary number.
159    pub fn log(&self) -> usize {
160        self.digits.len()
161    }
162
163    /// Retrieves a slice containing the digits of the `Ternary`.
164    ///
165    /// # Returns
166    ///
167    /// A slice referencing the digits vec of the `Ternary`.
168    ///
169    /// This function allows access to the raw representation of the
170    /// balanced ternary number as a slice of `Digit` values.
171    pub fn to_digit_slice(&self) -> &[Digit] {
172        self.digits.as_slice()
173    }
174
175    /// Returns a reference to the [Digit] indexed by `index` if it exists.
176    ///
177    /// Digits are indexed **from the right**:
178    /// ```
179    /// use balanced_ternary::Ternary;
180    ///
181    /// // Indexes :
182    /// //                              32
183    /// //                             4||1
184    /// //                            5||||0
185    /// //                            ||||||
186    /// //                            vvvvvv
187    /// let ternary = Ternary::parse("+++--+");
188    /// assert_eq!(ternary.get_digit(1).unwrap().to_char(), '-')
189    /// ```
190    pub fn get_digit(&self, index: usize) -> Option<&Digit> {
191        self.digits.iter().rev().nth(index)
192    }
193
194    /// Parses a string representation of a balanced ternary number into a `Ternary` object.
195    ///
196    /// Each character in the string must be one of `+`, `0`, or `-`.
197    pub fn parse(str: &str) -> Self {
198        let mut repr = Ternary::new(vec![]);
199        for c in str.chars() {
200            repr.digits.push(Digit::from_char(c));
201        }
202        repr
203    }
204
205    /// Converts the `Ternary` object to its integer (decimal) representation.
206    ///
207    /// Calculates the sum of each digit's value multiplied by the appropriate power of 3.
208    pub fn to_dec(&self) -> i64 {
209        let mut dec = 0;
210        for (rank, digit) in self.digits.iter().rev().enumerate() {
211            dec += digit.to_i8() as i64 * 3_i64.pow(rank as u32);
212        }
213        dec
214    }
215
216    /// Creates a balanced ternary number from a decimal integer.
217    ///
218    /// The input number is converted into its balanced ternary representation,
219    /// with digits represented as `Digit`s.
220    pub fn from_dec(dec: i64) -> Self {
221        let sign = dec.signum();
222        let str = format_radix(dec.abs(), 3);
223        let mut carry = 0u8;
224        let mut repr = Ternary::new(vec![]);
225        for digit in str.chars().rev() {
226            let digit = u8::from_str(&digit.to_string()).unwrap() + carry;
227            if digit < 2 {
228                repr.digits.push(Digit::from_i8(digit as i8));
229                carry = 0;
230            } else if digit == 2 {
231                repr.digits.push(Digit::from_i8(-1));
232                carry = 1;
233            } else if digit == 3 {
234                repr.digits.push(Digit::from_i8(0));
235                carry = 1;
236            } else {
237                panic!("Ternary::from_dec(): Invalid digit: {}", digit);
238            }
239        }
240        if carry == 1 {
241            repr.digits.push(Digit::from_i8(1));
242        }
243        repr.digits.reverse();
244        if sign == -1 {
245            -&repr
246        } else {
247            repr
248        }
249    }
250
251    /// Converts the balanced ternary number to its unbalanced representation as a string.
252    ///
253    /// The unbalanced representation treats the digits as standard ternary (0, 1, 2),
254    /// instead of balanced ternary (-1, 0, +1). Negative digits are handled by
255    /// calculating the decimal value of the balanced ternary number and converting
256    /// it back to an unbalanced ternary string.
257    ///
258    /// Returns:
259    /// * `String` - The unbalanced ternary representation of the number, where each
260    /// digit is one of `0`, `1`, or `2`.
261    ///
262    /// Example:
263    /// ```
264    /// use balanced_ternary::Ternary;
265    ///
266    /// let repr = Ternary::parse("+--");
267    /// assert_eq!(repr.to_unbalanced(), "12");
268    /// assert_eq!(repr.to_dec(), 5);
269    /// let repr = Ternary::parse("-++");
270    /// assert_eq!(repr.to_unbalanced(), "-12");
271    /// assert_eq!(repr.to_dec(), -5);
272    /// ```
273    pub fn to_unbalanced(&self) -> String {
274        format_radix(self.to_dec(), 3)
275    }
276
277    /// Parses a string representation of an unbalanced ternary number into a `Ternary` object.
278    ///
279    /// The string must only contain characters valid in the unbalanced ternary numeral system (`0`, `1`, or `2`).
280    /// Each character is directly converted into its decimal value and then interpreted as a balanced ternary number.
281    ///
282    /// # Arguments
283    ///
284    /// * `unbalanced` - A string slice representing the unbalanced ternary number.
285    ///
286    /// # Returns
287    ///
288    /// A `Ternary` object representing the same value as the input string in balanced ternary form.
289    ///
290    /// # Panics
291    ///
292    /// This function will panic if the string is not a valid unbalanced ternary number.
293    /// For instance, if it contains characters other than `0`, `1`, or `2`.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use balanced_ternary::Ternary;
299    ///
300    /// let ternary = Ternary::from_unbalanced("-12");
301    /// assert_eq!(ternary.to_string(), "-++");
302    /// assert_eq!(ternary.to_dec(), -5);
303    /// ```
304    pub fn from_unbalanced(unbalanced: &str) -> Self {
305        Self::from_dec(i64::from_str_radix(unbalanced, 3).unwrap())
306    }
307
308    /// Applies a transformation function to each digit of the balanced ternary number,
309    /// returning a new `Ternary` object with the transformed digits.
310    ///
311    /// This method keeps the order of the digits unchanged while applying the provided
312    /// transformation function `f` to each digit individually.
313    ///
314    /// # Arguments
315    ///
316    /// * `f` - A closure or function that takes a `Digit` and returns a transformed `Digit`.
317    ///
318    /// # Returns
319    ///
320    /// * `Self` - A new `Ternary` object containing the transformed digits.
321    ///
322    /// # Digit transformations
323    ///
324    /// These methods (unary operators) from the [Digit] type can be called directly.
325    ///
326    /// * Returns either `Pos` or `Neg`:
327    ///     * [Digit::possibly]
328    ///     * [Digit::necessary]
329    ///     * [Digit::contingently]
330    ///     * [Digit::ht_not]
331    /// * Returns either `Zero` or `Pos` or `Neg`.
332    ///     * [Digit::pre]
333    ///     * [Digit::post]
334    ///     * `Digit::not`
335    ///     * `Digit::neg`
336    ///     * [Digit::absolute_positive]
337    ///     * [Digit::positive]
338    ///     * [Digit::not_negative]
339    ///     * [Digit::not_positive]
340    ///     * [Digit::negative]
341    ///     * [Digit::absolute_negative]
342    ///
343    /// # Examples
344    /// ```
345    /// use balanced_ternary::{Ternary, Digit};
346    ///
347    /// let orig_ternary = Ternary::parse("+0-");
348    /// let transformed = orig_ternary.each(Digit::necessary);
349    /// assert_eq!(transformed.to_string(), "+--");
350    /// let transformed = orig_ternary.each(Digit::positive);
351    /// assert_eq!(transformed.to_string(), "+00");
352    /// let transformed = orig_ternary.each(Digit::not_negative);
353    /// assert_eq!(transformed.to_string(), "++0");
354    /// let transformed = orig_ternary.each(Digit::absolute_negative);
355    /// assert_eq!(transformed.to_string(), "-0-");
356    /// ```
357    pub fn each(&self, f: impl Fn(Digit) -> Digit) -> Self {
358        let mut repr = Ternary::new(vec![]);
359        for digit in self.digits.iter() {
360            repr.digits.push(f(*digit));
361        }
362        repr
363    }
364
365    /// Applies a transformation function to each digit of the balanced ternary number,
366    /// using an additional parameter for the transformation process, returning a new `Ternary`
367    /// object with the transformed digits.
368    ///
369    /// This method keeps the order of the digits unchanged while applying the provided
370    /// transformation function `f` to each digit individually, along with the provided extra
371    /// `other` digit.
372    ///
373    /// # Arguments
374    ///
375    /// * `f` - A closure or function that takes a `Digit` and an additional `Digit`,
376    ///         and returns a transformed `Digit`.
377    /// * `other` - An additional `Digit` to be passed to the transformation function `f`.
378    ///
379    /// # Returns
380    ///
381    /// * `Self` - A new `Ternary` object containing the transformed digits.
382    ///
383    /// # Digit transformations
384    ///
385    /// These methods (binary operators) from the [Digit] type can be called directly.
386    ///
387    /// * `Digit::mul`
388    /// * `Digit::div`
389    /// * `Digit::bitand` (k3/l3 and)
390    /// * [Digit::bi3_and]
391    /// * `Digit::bitor`  (k3/l3 or)
392    /// * [Digit::bi3_or]
393    /// * `Digit::bitxor` (k3/l3 xor)
394    /// * [Digit::k3_imply]
395    /// * [Digit::k3_equiv]
396    /// * [Digit::bi3_imply]
397    /// * [Digit::l3_imply]
398    /// * [Digit::rm3_imply]
399    /// * [Digit::ht_imply]
400    ///
401    /// # Examples
402    /// ```
403    /// use std::ops::Mul;
404    /// use balanced_ternary::{Ternary, Digit};
405    ///
406    /// let original = Ternary::parse("+-0");
407    /// let transformed = original.each_with(Digit::mul, Digit::Neg);
408    /// assert_eq!(transformed.to_string(), "-+0");
409    /// ```
410    pub fn each_with(&self, f: impl Fn(Digit, Digit) -> Digit, other: Digit) -> Self {
411        let mut repr = Ternary::new(vec![]);
412        for digit in self.digits.iter() {
413            repr.digits.push(f(*digit, other));
414        }
415        repr
416    }
417
418    /// Applies a transformation function to each digit of the balanced ternary number,
419    /// along with a corresponding digit from another `Ternary` number.
420    ///
421    /// This method ensures that the digits of both `Ternary` objects are aligned from the least
422    /// significant to the most significant digit. If the `other` `Ternary` has fewer digits
423    /// than the current one, the process is reversed to handle the shorter `Ternary` consistently.
424    /// The result is a new `Ternary` object where each digit was transformed using the provided function `f`.
425    ///
426    /// # Arguments
427    ///
428    /// * `f` - A closure or function that takes two arguments:
429    ///     * a `Digit` from the current `Ternary`,
430    ///     * a `Digit` from the corresponding position in the `other` `Ternary`.
431    ///     * The function must return a transformed `Digit`.
432    /// * `other` - A `Ternary` object with digits to process alongside the digits of the current object.
433    ///
434    /// # Returns
435    ///
436    /// * `Self` - A new `Ternary` object containing the transformed digits.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use std::ops::Mul;
442    /// use balanced_ternary::{Ternary, Digit};
443    ///
444    /// let ternary1 = Ternary::parse("-+0-+0-+0");
445    /// let ternary2 = Ternary::parse("---000+++");
446    ///
447    /// let result = ternary1.each_zip(Digit::mul, ternary2.clone());
448    /// assert_eq!(result.to_string(), "+-0000-+0");
449    ///
450    /// let result = ternary1.each_zip(Digit::k3_imply, ternary2.clone());
451    /// assert_eq!(result.to_string(), "+-0+00+++");
452    /// let result = ternary1.each_zip(Digit::bi3_imply, ternary2.clone());
453    /// assert_eq!(result.to_string(), "+-0000++0");
454    /// let result = ternary1.each_zip(Digit::ht_imply, ternary2.clone());
455    /// assert_eq!(result.to_string(), "+--+0++++");
456    /// ```
457    pub fn each_zip(&self, f: impl Fn(Digit, Digit) -> Digit, other: Self) -> Self {
458        if self.digits.len() < other.digits.len() {
459            return other.each_zip(f, self.clone());
460        }
461        let mut repr = Ternary::new(vec![]);
462        for (i, digit) in self.digits.iter().rev().enumerate() {
463            let d_other = other.get_digit(i).unwrap();
464            let res = f(*digit, *d_other);
465            repr.digits.push(res);
466        }
467        repr.digits.reverse();
468        repr
469    }
470
471    /// Applies a transformation function to each digit of the balanced ternary number,
472    /// along with a corresponding digit from another `Ternary` number, and a carry digit.
473    ///
474    /// This method processes the digits in reverse order (from the least significant to the most significant),
475    /// keeping their transformed order correct by reversing the result afterward. Each digit from the
476    /// current `Ternary` object is processed with the corresponding digit from another `Ternary` object
477    /// and a carry digit using the provided closure or function `f`.
478    ///
479    /// # Arguments
480    ///
481    /// * `f` - A closure or function that takes three arguments:
482    ///     * a `Digit` from the current `Ternary`,
483    ///     * a `Digit` from the corresponding position in the `other` `Ternary`,
484    ///     * and the current carry `Digit`.
485    ///     * The function must return a tuple containing `(carry: Digit, transformed: Digit)`.
486    /// * `other` - A `Ternary` object with digits to process alongside the digits of the current object.
487    ///
488    /// # Returns
489    ///
490    /// * `Self` - A new `Ternary` object containing the transformed digits.
491    ///
492    /// # Notes
493    ///
494    /// The carry digit is initially `Zero` and is passed between each step of the transformation process.
495    /// If the `other` `Ternary` has fewer digits than the current one, the missing digits in `other`
496    /// are treated as `Zero`.
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use balanced_ternary::{Digit, Ternary};
502    ///
503    /// let ternary1 = Ternary::parse("+-0");
504    /// let ternary2 = Ternary::parse("-+0");
505    ///
506    /// // Transformation function that adds digits with a carry digit
507    /// let combine = |d1: Digit, d2: Digit, carry: Digit| -> (Digit, Digit) {
508    ///     // Simple example operation: this just illustrates transforming with carry.
509    ///     // Replace with meaningful logic as needed for your application.
510    ///     let sum = d1.to_i8() + d2.to_i8() + carry.to_i8();
511    ///     (Digit::from_i8(sum / 3), Digit::from_i8(sum % 3))
512    /// };
513    ///
514    /// let result = ternary1.each_zip_carry(combine, ternary2.clone()).trim();
515    /// assert_eq!(result.to_string(), (&ternary1 + &ternary2).to_string());
516    /// ```
517    pub fn each_zip_carry(
518        &self,
519        f: impl Fn(Digit, Digit, Digit) -> (Digit, Digit),
520        other: Self,
521    ) -> Self {
522        if self.digits.len() < other.digits.len() {
523            return other.each_zip_carry(f, self.clone());
524        }
525        let mut repr = Ternary::new(vec![]);
526        let mut carry = Zero;
527        for (i, digit) in self.digits.iter().rev().enumerate() {
528            let d_other = other.get_digit(i).unwrap();
529            let (c, res) = f(*digit, *d_other, carry);
530            carry = c;
531            repr.digits.push(res);
532        }
533        repr.digits.reverse();
534        repr
535    }
536
537    /// Removes leading `Zero` digits from the `Ternary` number, effectively trimming
538    /// it down to its simplest representation. The resulting `Ternary` number
539    /// will still represent the same value.
540    ///
541    /// # Returns
542    ///
543    /// * `Self` - A new `Ternary` object, trimmed of leading zeros.
544    ///
545    /// # Examples
546    ///
547    /// ```
548    /// use balanced_ternary::{ Neg, Pos, Ternary, Zero};
549    ///
550    /// let ternary = Ternary::new(vec![Zero, Zero, Pos, Neg]);
551    /// let trimmed = ternary.trim();
552    /// assert_eq!(trimmed.to_string(), "+-");
553    /// ```
554    ///
555    /// # Notes
556    ///
557    /// This method does not mutate the original `Ternary` object but returns a new representation.
558    pub fn trim(&self) -> Self {
559        if self.to_dec() == 0 {
560            return Ternary::parse("0");
561        }
562        let mut repr = Ternary::new(vec![]);
563        let mut first_digit = false;
564        for digit in self.digits.iter() {
565            if !first_digit && digit != &Zero {
566                first_digit = true;
567            }
568            if first_digit {
569                repr.digits.push(*digit);
570            }
571        }
572        repr
573    }
574
575    /// Adjusts the representation of the `Ternary` number to have a fixed number of digits.
576    ///
577    /// If the current `Ternary` has fewer digits than the specified `length`, leading zero digits
578    /// will be added to the `Ternary` to match the desired length. If the current `Ternary` has
579    /// more digits than the specified `length`, it will be returned unmodified.
580    ///
581    /// # Arguments
582    ///
583    /// * `length` - The desired length of the `Ternary` number.
584    ///
585    /// # Returns
586    ///
587    /// * `Self` - A new `Ternary` object with the specified fixed length.
588    ///
589    /// # Notes
590    ///
591    /// If `length` is smaller than the existing number of digits, the function does not truncate
592    /// the number but instead returns the original `Ternary` unchanged.
593    ///
594    /// # Examples
595    ///
596    /// ```
597    /// use balanced_ternary::{Ternary, Zero, Pos};
598    ///
599    /// let ternary = Ternary::new(vec![Pos]);
600    /// let fixed = ternary.with_length(5);
601    /// assert_eq!(fixed.to_string(), "0000+");
602    ///
603    /// let fixed = ternary.with_length(1);
604    /// assert_eq!(fixed.to_string(), "+");
605    /// ```
606    pub fn with_length(&self, length: usize) -> Self {
607        if length < self.log() {
608            return self.clone();
609        }
610        let zeroes = vec![Zero; length - self.log()];
611        let mut repr = Ternary::new(vec![]);
612        repr.digits.extend(zeroes);
613        repr.digits.extend(self.digits.iter().cloned());
614        repr
615    }
616
617    /// Converts the `Ternary` number into a string representation by applying a given
618    /// transformation function to each digit of the ternary number.
619    ///
620    /// # Arguments
621    ///
622    /// * `transform` - A function or closure that takes a `Digit` and returns a `char`, representing the digit.
623    ///
624    /// # Returns
625    ///
626    /// A `String`-based representation of the `Ternary` number resulting from
627    /// applying the transformation to its digits.
628    ///
629    /// # Examples
630    ///
631    /// ```
632    /// use balanced_ternary::{Digit, Pos, Neg, Zero, Ternary};
633    ///
634    /// let ternary = Ternary::new(vec![Pos, Zero, Neg]);
635    ///
636    /// let custom_repr = ternary.to_string_repr(Digit::to_char_t);
637    /// assert_eq!(custom_repr, "10T");
638    /// let custom_repr = ternary.to_string_repr(Digit::to_char_theta);
639    /// assert_eq!(custom_repr, "10Θ");
640    /// let custom_repr = ternary.to_string_repr(Digit::to_char);
641    /// assert_eq!(custom_repr, "+0-");
642    /// ```
643    ///
644    /// # Notes
645    ///
646    /// * The function provides flexibility to define custom string representations
647    ///   for the ternary number digits.
648    /// * Call to `Ternary::to_string()` is equivalent to `Ternary::to_string_repr(Digit::to_char)`.
649    pub fn to_string_repr<F: Fn(&Digit) -> char>(&self, transform: F) -> String {
650        let mut str = String::new();
651        for digit in self.digits.iter() {
652            str.push(transform(digit));
653        }
654        str
655    }
656
657    /// Concatenates the current `Ternary` number with another `Ternary` number.
658    ///
659    /// This function appends the digits of the provided `Ternary` object to the digits
660    /// of the current `Ternary` object, creating a new `Ternary` number as the result.
661    ///
662    /// # Arguments
663    ///
664    /// * `other` - A reference to the `Ternary` number to be concatenated to the current one.
665    ///
666    /// # Returns
667    ///
668    /// * `Ternary` - A new `Ternary` object formed by concatenating the digits.
669    ///
670    /// # Examples
671    ///
672    /// ```
673    /// use balanced_ternary::{Ternary, Pos, Zero, Neg};
674    ///
675    /// let ternary1 = Ternary::new(vec![Pos, Zero]);
676    /// let ternary2 = Ternary::new(vec![Neg, Pos]);
677    ///
678    /// let concatenated = ternary1.concat(&ternary2);
679    /// assert_eq!(concatenated.to_string(), "+0-+");
680    /// ```
681    pub fn concat(&self, other: &Ternary) -> Ternary {
682        let mut t = Ternary::new(vec![]);
683        t.digits.extend(self.digits.iter().cloned());
684        t.digits.extend(other.digits.iter().cloned());
685        t
686    }
687}
688
689#[cfg(feature = "ternary-string")]
690impl Display for Ternary {
691    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
692        write!(f, "{}", self.to_string_repr(Digit::to_char))
693    }
694}
695
696#[cfg(feature = "ternary-string")]
697mod operations;
698
699mod conversions;
700
701#[cfg(feature = "tryte")]
702mod tryte;
703
704#[cfg(feature = "tryte")]
705pub use crate::tryte::Tryte;
706
707#[cfg(test)]
708#[cfg(feature = "ternary-string")]
709#[test]
710fn test_ternary() {
711    use crate::*;
712
713    let repr5 = Ternary::new(vec![Pos, Neg, Neg]);
714    assert_eq!(repr5.to_dec(), 5);
715    let repr5 = Ternary::from_dec(5);
716    assert_eq!(repr5.to_dec(), 5);
717
718    let repr13 = Ternary::new(vec![Pos, Pos, Pos]);
719    assert_eq!(repr13.to_dec(), 13);
720
721    let repr14 = Ternary::parse("+---");
722    let repr15 = Ternary::parse("+--0");
723    assert_eq!(repr14.to_dec(), 14);
724    assert_eq!(repr15.to_dec(), 15);
725    assert_eq!(repr14.to_string(), "+---");
726    assert_eq!(repr15.to_string(), "+--0");
727
728    let repr120 = Ternary::from_dec(120);
729    assert_eq!(repr120.to_dec(), 120);
730    assert_eq!(repr120.to_string(), "++++0");
731    let repr121 = Ternary::from_dec(121);
732    assert_eq!(repr121.to_dec(), 121);
733    assert_eq!(repr121.to_string(), "+++++");
734
735    let repr_neg_5 = Ternary::parse("-++");
736    assert_eq!(repr_neg_5.to_dec(), -5);
737    assert_eq!(repr_neg_5.to_string(), "-++");
738
739    let repr_neg_5 = Ternary::from_dec(-5);
740    assert_eq!(repr_neg_5.to_dec(), -5);
741    assert_eq!(repr_neg_5.to_string(), "-++");
742
743    let repr_neg_121 = Ternary::from_dec(-121);
744    assert_eq!(repr_neg_121.to_dec(), -121);
745    assert_eq!(repr_neg_121.to_string(), "-----");
746
747    let test = Ternary::from_dec(18887455);
748    assert_eq!(test.to_dec(), 18887455);
749    assert_eq!(test.to_string(), "++00--0--+-0++0+");
750
751    let unbalanced = Ternary::from_unbalanced("12");
752    assert_eq!(unbalanced.to_dec(), 5);
753    assert_eq!(unbalanced.to_string(), "+--");
754
755    let unbalanced = Ternary::from_unbalanced("-12");
756    assert_eq!(unbalanced.to_dec(), -5);
757    assert_eq!(unbalanced.to_string(), "-++");
758
759    let unbalanced = Ternary::from_dec(121);
760    assert_eq!(unbalanced.to_unbalanced(), "11111");
761    assert_eq!(unbalanced.to_string(), "+++++");
762}
763
764#[cfg(test)]
765#[cfg(feature = "ternary-string")]
766#[test]
767fn test_operations() {
768    fn test_ternary_eq(a: Ternary, b: &str) {
769        let repr = Ternary::parse(b);
770        assert_eq!(a.to_string(), repr.to_string());
771    }
772    fn test_binary_op(a: &Ternary, f: impl Fn(Digit, Digit) -> Digit, b: &Ternary, c: &str) {
773        test_ternary_eq(a.each_zip(f, b.clone()), c);
774    }
775
776    use core::ops::{BitAnd, BitOr, BitXor, Mul, Not};
777
778    let short = Ternary::parse("-0+");
779    let long = Ternary::parse("---000+++");
780    let other = Ternary::parse("-0+-0+-0+");
781
782    // K3
783    test_ternary_eq(short.each(Digit::not), "+0-");
784    test_binary_op(&long, Digit::bitand, &other, "----00-0+");
785    test_binary_op(&long, Digit::bitor, &other, "-0+00++++");
786    test_binary_op(&long, Digit::bitxor, &other, "-0+000+0-");
787    test_binary_op(&long, Digit::k3_equiv, &other, "+0-000-0+");
788    test_binary_op(&long, Digit::k3_imply, &other, "+++00+-0+");
789
790    // HT
791    test_ternary_eq(short.each(Digit::ht_not), "+--");
792    test_binary_op(&long, Digit::ht_imply, &other, "+++-++-0+");
793
794    // BI3
795    test_binary_op(&long, Digit::bi3_and, &other, "-0-000-0+");
796    test_binary_op(&long, Digit::bi3_or, &other, "-0+000+0+");
797    test_binary_op(&long, Digit::bi3_imply, &other, "+0+000-0+");
798
799    // L3
800    test_ternary_eq(short.each(Digit::possibly), "-++");
801    test_ternary_eq(short.each(Digit::necessary), "--+");
802    test_ternary_eq(short.each(Digit::contingently), "-+-");
803    test_binary_op(&long, Digit::l3_imply, &other, "+++0++-0+");
804
805    // PARA / RM3
806    test_binary_op(&long, Digit::rm3_imply, &other, "+++-0+--+");
807    test_binary_op(&long, Digit::para_imply, &other, "+++-0+-0+");
808
809    // Other operations
810    test_ternary_eq(short.each(Digit::post), "0+-");
811    test_ternary_eq(short.each(Digit::pre), "+-0");
812    test_ternary_eq(short.each(Digit::absolute_positive), "+0+");
813    test_ternary_eq(short.each(Digit::positive), "00+");
814    test_ternary_eq(short.each(Digit::not_negative), "0++");
815    test_ternary_eq(short.each(Digit::not_positive), "--0");
816    test_ternary_eq(short.each(Digit::negative), "-00");
817    test_ternary_eq(short.each(Digit::absolute_negative), "-0-");
818
819    test_binary_op(&long, Digit::mul, &other, "+0-000-0+");
820}