Skip to main content

malachite_base/unsigned_polynomial/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::num::basic::traits::Zero;
10use crate::num::basic::unsigneds::PrimitiveUnsigned;
11use crate::polynomial::Polynomial;
12use crate::unsigned_polynomial::UnsignedPolynomial;
13use crate::vars::xyz::XyzVars;
14use crate::vars::{Var, VarScheme, char_is_reserved};
15use alloc::vec::Vec;
16use core::str::FromStr;
17
18// Reads the variable-and-exponent part of a term, such as `x` or `x^2`, giving the exponent.
19//
20// The name must be the one `var` goes by; a name some other variable of the same scheme goes by is
21// no more acceptable than a name from another scheme altogether, since a polynomial in one variable
22// has nowhere to put a second. An exponent of 0 is refused, as it is what a term with no variable
23// would have, and that term is written as its coefficient alone.
24fn parse_monic<S: VarScheme + ?Sized>(var: Var<'_, S>, monic: &str) -> Option<usize> {
25    let (name, exponent) = match monic.split_once('^') {
26        Some((name, exponent)) => {
27            let exponent = usize::from_str(exponent).ok()?;
28            if exponent == 0 {
29                return None;
30            }
31            (name, exponent)
32        }
33        None => (monic, 1),
34    };
35    if var.scheme().parse_var(name) == Some(var.index()) {
36        Some(exponent)
37    } else {
38        None
39    }
40}
41
42// Reads one term, giving its coefficient and the exponent of the variable in it.
43//
44// A term is a coefficient, or a coefficient and a variable part with a `*` between them, or a
45// variable part alone, whose coefficient is 1. Which of the three it is can be told from its first
46// character: a name holds no reserved character, and a coefficient is all digits, so a term that
47// begins with an unreserved character is one that begins with a name.
48//
49// A coefficient of 0 is refused. A term that is zero contributes nothing, and is not written at
50// all; the one polynomial with nothing to write is the zero polynomial, which is written `0`, and
51// that is read before this is ever reached.
52fn parse_term<T: PrimitiveUnsigned, S: VarScheme + ?Sized>(
53    var: Var<'_, S>,
54    term: &str,
55) -> Option<(T, usize)> {
56    if term.starts_with(|c: char| !char_is_reserved(c)) {
57        return Some((T::ONE, parse_monic(var, term)?));
58    }
59    let (coefficient, exponent) = match term.split_once('*') {
60        Some((coefficient, monic)) => (coefficient, parse_monic(var, monic)?),
61        None => (term, 0),
62    };
63    let coefficient = T::from_str(coefficient).ok()?;
64    if coefficient == T::ZERO {
65        None
66    } else {
67        Some((coefficient, exponent))
68    }
69}
70
71// The implementation of `UnsignedPolynomial::from_string_with`.
72pub(crate) fn from_string_with<T: PrimitiveUnsigned, S: VarScheme + ?Sized>(
73    var: Var<'_, S>,
74    s: &str,
75) -> Option<UnsignedPolynomial<T>> {
76    // The zero polynomial has no terms, so it is the one string that the loop below could not read.
77    if s == "0" {
78        return Some(UnsignedPolynomial::<T>::ZERO);
79    }
80    let mut coefficients: Vec<T> = Vec::new();
81    for term in s.split('+') {
82        let (coefficient, exponent) = parse_term(var, term)?;
83        if exponent >= coefficients.len() {
84            coefficients.resize(exponent + 1, T::ZERO);
85        } else if coefficients[exponent] != T::ZERO {
86            // Two terms of the same degree. Which of them to believe is not for this to decide.
87            return None;
88        }
89        coefficients[exponent] = coefficient;
90    }
91    Some(UnsignedPolynomial::<T>::from_coefficients_asc(coefficients))
92}
93
94impl<T: PrimitiveUnsigned> FromStr for UnsignedPolynomial<T> {
95    type Err = ();
96
97    /// Converts a string to a [`UnsignedPolynomial`].
98    ///
99    /// The variable is called `x`. [`from_string_with`](UnsignedPolynomial::from_string_with) is
100    /// the way to call it something else.
101    ///
102    /// This reads back everything [`Display`](core::fmt::Display) writes, and more besides: the
103    /// terms may come in any order, an exponent may be written `^1`, and a coefficient may have
104    /// leading zeros. What it will not accept is a term whose coefficient is zero, two terms of the
105    /// same degree, a variable other than the one asked for, or anything with a space in it. The
106    /// zero polynomial is `0`, and is the only string in which a zero coefficient may be written.
107    ///
108    /// If the string does not represent a [`UnsignedPolynomial`], an `Err` is returned.
109    ///
110    /// # Worst-case complexity
111    /// $T(n) = O(n (\log n)^2 \log\log n)$
112    ///
113    /// $M(n) = O(n \log n)$
114    ///
115    /// where $T$ is time, $M$ is additional memory, and $n$ is `s.len()`.
116    ///
117    /// # Examples
118    /// ```
119    /// use core::str::FromStr;
120    /// use malachite_base::unsigned_polynomial::UnsignedPolynomial;
121    ///
122    /// assert_eq!(
123    ///     UnsignedPolynomial::<u64>::from_str("x^2+3*x+2")
124    ///         .unwrap()
125    ///         .to_string(),
126    ///     "x^2+3*x+2"
127    /// );
128    /// assert_eq!(
129    ///     UnsignedPolynomial::<u64>::from_str("0")
130    ///         .unwrap()
131    ///         .to_string(),
132    ///     "0"
133    /// );
134    /// assert_eq!(
135    ///     UnsignedPolynomial::<u64>::from_str("5")
136    ///         .unwrap()
137    ///         .to_string(),
138    ///     "5"
139    /// );
140    /// assert_eq!(
141    ///     UnsignedPolynomial::<u64>::from_str("x")
142    ///         .unwrap()
143    ///         .to_string(),
144    ///     "x"
145    /// );
146    ///
147    /// // The terms may come in any order.
148    /// assert_eq!(
149    ///     UnsignedPolynomial::<u64>::from_str("2+3*x+x^2")
150    ///         .unwrap()
151    ///         .to_string(),
152    ///     "x^2+3*x+2"
153    /// );
154    ///
155    /// assert!(UnsignedPolynomial::<u64>::from_str("").is_err());
156    /// assert!(UnsignedPolynomial::<u64>::from_str("y").is_err());
157    /// assert!(UnsignedPolynomial::<u64>::from_str("x^2 + 1").is_err());
158    /// assert!(UnsignedPolynomial::<u64>::from_str("0*x").is_err());
159    /// assert!(UnsignedPolynomial::<u64>::from_str("x+x").is_err());
160    /// assert!(UnsignedPolynomial::<u64>::from_str("-x").is_err());
161    /// ```
162    #[inline]
163    fn from_str(s: &str) -> Result<Self, ()> {
164        Self::from_string_with(Var::new(&XyzVars, 0), s).ok_or(())
165    }
166}