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