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}