leetcode/valid_number.rs
1//! # 65. 有效数字
2//!
3//! 难度 困难
4//!
5//! 验证给定的字符串是否可以解释为十进制数字。
6//!
7//! 例如:
8//!
9//! - `"0"` => `true`
10//! - `" 0.1 "` => `true`
11//! - `"abc"` => `false`
12//! - `"1 a"` => `false`
13//! - `"2e10"` => `true`
14//! - `" -90e3 "` => `true`
15//! - `" 1e"` => `false`
16//! - `"e3"` => `false`
17//! - `" 6e-1"` => `true`
18//! - `" 99e2.5 "` => `false`
19//! - `"53.5e93"` => `true`
20//! - `" --6 "` => `false`
21//! - `"-+3"` => `false`
22//! - `"95a54e53"` => `false`
23//!
24//! 说明: 我们有意将问题陈述地比较模糊。在实现代码之前,你应当事先思考所有可能的情况。这里给出一份可能存在于有效十进制数字中的字符列表:
25//!
26//! - 数字 0-9
27//! - 指数 - "e"
28//! - 正/负号 - "+"/"-"
29//! - 小数点 - "."
30//!
31//! 当然,在输入中,这些字符的上下文也很重要。
32//!
33//! See [leetcode](https://leetcode-cn.com/problems/valid-number/)
34
35/// DFA
36///
37/// `enum` + Pattern Matching 实现状态机,再加上 `Iterator`/`Result` 的高阶函数组合工具,
38/// 代码可读性比写个表找下标不知道高哪里去了。
39///
40/// 另外,开始不需要 `trim()`,一是因为这个操作结合起来整个实现并没有这里的实现高效,
41/// 二是内部调用了 `char:is_whitespace()` 不止判断了空格,还有其它空白字符,不一定符合题目的测试用例。
42pub struct Solution;
43
44impl Solution {
45 pub fn is_number(s: String) -> bool {
46 s.chars()
47 .try_fold(State::new(), State::handle)
48 .as_ref()
49 .map_or(false, State::is_valid)
50 }
51}
52
53type Result = std::result::Result<State, ()>;
54
55enum State {
56 Start,
57 Sign,
58 Integer,
59 Dot,
60 EmptyDot,
61 Decimal,
62 E,
63 ExpSign,
64 Exponent,
65 End,
66}
67
68impl State {
69 fn new() -> Self {
70 State::Start
71 }
72
73 fn is_valid(&self) -> bool {
74 use State::*;
75 match self {
76 Start | Sign | E | ExpSign | EmptyDot => false,
77 _ => true,
78 }
79 }
80
81 fn handle(self, c: char) -> Result {
82 use State::*;
83 match self {
84 Start => match c {
85 ' ' => Ok(Start),
86 '+' | '-' => Ok(Sign),
87 '0'..='9' => Ok(Integer),
88 '.' => Ok(EmptyDot),
89 _ => Err(()),
90 }
91 Sign => match c {
92 '0'..='9' => Ok(Integer),
93 '.' => Ok(EmptyDot),
94 _ => Err(()),
95 }
96 Integer => match c {
97 '0'..='9' => Ok(Integer),
98 '.' => Ok(Dot),
99 'e' => Ok(E),
100 ' ' => Ok(End),
101 _ => Err(()),
102 }
103 EmptyDot => match c {
104 '0'..='9' => Ok(Decimal), // " .1" or " +.1"
105 _ => Err(()),
106 }
107 Dot => match c {
108 '0'..='9' => Ok(Decimal),
109 'e' => Ok(E), // "46.e3"
110 ' ' => Ok(End),
111 _ => Err(()),
112 }
113 Decimal => match c {
114 '0'..='9' => Ok(Decimal),
115 'e' => Ok(E),
116 ' ' => Ok(End),
117 _ => Err(()),
118 }
119 E => match c {
120 '+' | '-' => Ok(ExpSign),
121 '0'..='9' => Ok(Exponent),
122 _ => Err(()),
123 }
124 ExpSign => match c {
125 '0'..='9' => Ok(Exponent),
126 _ => Err(()),
127 }
128 Exponent => match c {
129 '0'..='9' => Ok(Exponent),
130 ' ' => Ok(End),
131 _ => Err(()),
132 }
133 End => match c {
134 ' ' => Ok(End),
135 _ => Err(()),
136 }
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use std::str::FromStr;
145
146 #[test]
147 fn test() {
148 let cases = (
149 // false
150 vec![
151 "+.",
152 ".",
153 "1.0e4.5",
154 "-000111.xxaslkdfjaskldfj",
155 "12 3",
156 "1a3",
157 "",
158 " ",
159 "46.e",
160 "1e",
161 "+",
162 "-",
163 "+.",
164 "-.",
165 ],
166 // true
167 vec![
168 "123",
169 " 123 ",
170 "0",
171 "0123", //Cannot agree
172 "00", //Cannot agree
173 "-10",
174 "-0",
175 "123.5",
176 "123.000000",
177 "-500.777",
178 "0.0000001",
179 "0.00000",
180 "0.", //Cannot be more disagree!!!
181 "00.5", //Strongly cannot agree
182 "123e1",
183 "1.23e10",
184 "0.5e-10",
185 "0.5e04",
186 ".1", //Ok, if you say so
187 "2e0", //Really?!
188 "+.8",
189 " 005047e+6", //Damn = =|||
190 "-123.456e-789",
191 "46.e3",
192 ]);
193
194 let cases = cases.0.iter().map(|s| (false, s)).chain(cases.1.iter().map(|s| (true, s)));
195 for (expect, &s) in cases {
196 assert_eq!(expect, Solution::is_number(s.into()),
197 "expect is_number(\"{}\") = {}, but got {}, try parse: {:?}", s, expect, !expect, f32::from_str(s.trim()));
198 }
199 }
200}