Skip to main content

malachite_nz/gaussian_integer/conversion/string/
from_string.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::gaussian_integer::GaussianInteger;
10use crate::integer::Integer;
11use core::str::FromStr;
12use malachite_base::num::basic::traits::{NegativeOne, One, Zero};
13
14// An imaginary term standing alone: an optional sign, then an optional coefficient in `Integer`
15// syntax. A missing coefficient means 1, so "i" and "-i" work, but so do "1i" and "0i": requiring
16// producers to elide degenerate coefficients would force a special case on them, so the parser
17// accepts both spellings. A single leading '+' is accepted, exactly as `Integer`'s parser accepts
18// "+1".
19fn parse_lone_imaginary_coefficient(s: &str) -> Result<Integer, ()> {
20    match s {
21        "" | "+" => Ok(Integer::ONE),
22        "-" => Ok(Integer::NEGATIVE_ONE),
23        _ => Integer::from_str(s),
24    }
25}
26
27// An imaginary term joined to a preceding real term, starting with the joining sign; the sign alone
28// means a coefficient of 1 or -1.
29fn parse_joined_imaginary_coefficient(s: &str) -> Result<Integer, ()> {
30    match s {
31        "+" => Ok(Integer::ONE),
32        "-" => Ok(Integer::NEGATIVE_ONE),
33        _ => {
34            if let Some(digits) = s.strip_prefix('+') {
35                Integer::from_str(digits)
36            } else {
37                Integer::from_str(s)
38            }
39        }
40    }
41}
42
43impl FromStr for GaussianInteger {
44    type Err = ();
45
46    /// Converts a string to a [`GaussianInteger`].
47    ///
48    /// If the string does not represent a valid [`GaussianInteger`], an `Err` is returned. The
49    /// grammar is strict about structure: the real term must precede the imaginary term, the
50    /// imaginary term must end in `'i'`, and no whitespace is allowed. It is permissive about
51    /// coefficients, much as `Rational`'s parser accepts fractions that are not in lowest terms:
52    /// `"1i"`, `"0i"`, `"2+0i"`, and `"0+1i"` are all accepted, although
53    /// [`Display`](core::fmt::Display) never produces them. Each component follows [`Integer`]'s
54    /// syntax, so leading zeros are allowed, and so is a single leading `'-'` or `'+'` on the
55    /// leading term.
56    ///
57    /// # Worst-case complexity
58    /// $T(n) = O(n (\log n)^2 \log\log n)$
59    ///
60    /// $M(n) = O(n \log n)$
61    ///
62    /// where $T$ is time, $M$ is additional memory, and $n$ is `s.len()`.
63    ///
64    /// # Examples
65    /// ```
66    /// use core::str::FromStr;
67    /// use malachite_nz::gaussian_integer::GaussianInteger;
68    ///
69    /// assert_eq!(GaussianInteger::from_str("0").unwrap().to_string(), "0");
70    /// assert_eq!(GaussianInteger::from_str("-2").unwrap().to_string(), "-2");
71    /// assert_eq!(GaussianInteger::from_str("i").unwrap().to_string(), "i");
72    /// assert_eq!(GaussianInteger::from_str("-i").unwrap().to_string(), "-i");
73    /// assert_eq!(
74    ///     GaussianInteger::from_str("2-3i").unwrap().to_string(),
75    ///     "2-3i"
76    /// );
77    /// assert_eq!(GaussianInteger::from_str("1i").unwrap().to_string(), "i");
78    /// assert_eq!(GaussianInteger::from_str("0i").unwrap().to_string(), "0");
79    /// assert_eq!(GaussianInteger::from_str("2+0i").unwrap().to_string(), "2");
80    /// assert_eq!(
81    ///     GaussianInteger::from_str("+2+1i").unwrap().to_string(),
82    ///     "2+i"
83    /// );
84    ///
85    /// assert!(GaussianInteger::from_str("").is_err());
86    /// assert!(GaussianInteger::from_str("i+1").is_err());
87    /// assert!(GaussianInteger::from_str("1 + i").is_err());
88    /// assert!(GaussianInteger::from_str("2+-3i").is_err());
89    /// assert!(GaussianInteger::from_str("2ii").is_err());
90    /// ```
91    fn from_str(s: &str) -> Result<Self, ()> {
92        let Some(r) = s.strip_suffix('i') else {
93            // No imaginary term: the whole string is the real part.
94            return Ok(Self {
95                real: Integer::from_str(s)?,
96                imaginary: Integer::ZERO,
97            });
98        };
99        // The sign joining the real and imaginary terms is the last '+' or '-', except at the start
100        // of the string, where a '-' is the imaginary term's own sign. Any other sign characters
101        // are left inside a component, whose parse then fails.
102        match r.rfind(['+', '-']).filter(|&k| k != 0) {
103            None => Ok(Self {
104                real: Integer::ZERO,
105                imaginary: parse_lone_imaginary_coefficient(r)?,
106            }),
107            Some(k) => Ok(Self {
108                real: Integer::from_str(&r[..k])?,
109                imaginary: parse_joined_imaginary_coefficient(&r[k..])?,
110            }),
111        }
112    }
113}