Валидация числа
Условие
Напишите функцию, которая проверяет, является ли строка валидным числом (целым или с плавающей точкой).
Пример
Вход: 123 Выход: true
Вход: 12.5 Выход: true
Вход: 12a Выход: false
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Анализ задачи и подход к решению
Задача на валидацию строки как числа (целого или с плавающей точкой) классическая и часто встречается на собеседованиях. Она проверяет понимание конечных автоматов (Finite State Machine, FSM), умение работать с граничными случаями и писать чистый, тестируемый код.
Основная сложность — корректно обработать все возможные форматы числа без использования регулярных выражений (если это явно не разрешено) и встроенных функций преобразования строк в числа (например, parseInt, parseFloat, Number()), так как они могут некорректно обрабатывать некоторые случаи (например, parseInt("12.5") вернет 12, игнорируя .5).
Ключевые аспекты валидации
- Целая часть: Одна или более цифр. Может начинаться со знака
+или-. - Дробная часть (для чисел с плавающей точкой): Следует за точкой
., содержит одну или более цифр. - Порядок обработки: Строка должна быть обработана полностью, без лишних символов в конце.
- Дополнительные правила (по умолчанию): Обычно не разрешаются ведущие нули, если это не просто
0, научная нотация (например,1e5), пробелы в начале или конце, разделители тысяч (запятые). В условии задачи эти моменты часто уточняются.
Реализация на основе конечного автомата (FSM)
Наиболее надежный и понятный способ — реализовать детерминированный конечный автомат (DFA). Мы определяем состояния, в которых может находиться разбор строки, и правила перехода между ними в зависимости от текущего символа.
Определение состояний
- START: Начальное состояние.
- SIGN: Обнаружен знак (
+или-). - INTEGER: Обнаружена хотя бы одна цифра целой части.
- DOT: Обнаружена точка, но еще нет цифр дробной части.
- FRACTION: Обнаружена точка и хотя бы одна цифра дробной части.
- INVALID: Обнаружен некорректный символ или последовательность.
Правила переходов
- Из START:
* `+` или `-` -> **SIGN**
* цифра (`0-9`) -> **INTEGER**
* `.` -> **DOT**
* любой другой символ -> **INVALID**
- Из SIGN:
* цифра (`0-9`) -> **INTEGER**
* `.` -> **DOT**
* любой другой символ -> **INVALID**
- Из INTEGER:
* цифра (`0-9`) -> **INTEGER** (остаемся в том же состоянии)
* `.` -> **FRACTION** (переход к дробной части, т.к. целая часть уже есть)
* конец строки -> **Валидация успешна**
* любой другой символ -> **INVALID**
- Из DOT:
* цифра (`0-9`) -> **FRACTION**
* любой другой символ или конец строки -> **INVALID** (число не может заканчиваться на точку)
- Из FRACTION:
* цифра (`0-9`) -> **FRACTION** (остаемся в том же состоянии)
* конец строки -> **Валидация успешна**
* любой другой символ -> **INVALID**
Код реализации на JavaScript
function isValidNumber(str) {
// Определяем функцию-предикат для проверки, является ли символ цифрой
const isDigit = (ch) => ch >= '0' && ch <= '9';
let state = 'START';
for (let i = 0; i < str.length; i++) {
const ch = str[i];
switch (state) {
case 'START':
if (ch === '+' || ch === '-') {
state = 'SIGN';
} else if (isDigit(ch)) {
state = 'INTEGER';
} else if (ch === '.') {
state = 'DOT';
} else {
return false;
}
break;
case 'SIGN':
if (isDigit(ch)) {
state = 'INTEGER';
} else if (ch === '.') {
state = 'DOT';
} else {
return false;
}
break;
case 'INTEGER':
if (isDigit(ch)) {
// Остаемся в INTEGER
} else if (ch === '.') {
state = 'FRACTION';
} else {
return false; // Недопустимый символ в середине числа
}
break;
case 'DOT':
if (isDigit(ch)) {
state = 'FRACTION';
} else {
return false; // После точки должна быть цифра
}
break;
case 'FRACTION':
if (!isDigit(ch)) {
return false; // В дробной части допустимы только цифры
}
break;
default:
return false;
}
}
// Проверяем, находимся ли мы в допустимом конечном состоянии
// Допустимые конечные состояния: INTEGER (целое число) или FRACTION (дробное число)
return state === 'INTEGER' || state === 'FRACTION';
}
// Тесты
console.log(isValidNumber("123")); // true
console.log(isValidNumber("12.5")); // true
console.log(isValidNumber("12a")); // false
console.log(isValidNumber("-5.7")); // true
console.log(isValidNumber("+3.14")); // true
console.log(isValidNumber(".")); // false
console.log(isValidNumber("12.")); // false
console.log(isValidNumber(".5")); // true
console.log(isValidNumber("+-5")); // false
console.log(isValidNumber("")); // false
Альтернативные подходы
- Регулярные выражения (Regex): Самое простое решение, если оно разрешено.
function isValidNumberRegex(str) { // ^ - начало строки, [+-]? - необязательный знак, // \d+ - одна или более цифр (целая часть), // (\.\d+)? - необязательная дробная часть (точка и одна или более цифр), // $ - конец строки. return /^[+-]?\d+(\.\d+)?$/.test(str); }
Однако на собеседовании часто просят реализовать алгоритм "вручную", чтобы оценить логическое мышление.
- Инкрементальный разбор (парсинг) без явного FSM: Можно использовать флаги (
hasDigit,hasDot,hasSign), но это часто приводит к более сложной и запутанной логике с множеством условий.
Важные вопросы для уточнения у интервьюера
- Разрешены ли пробелы в начале или конце строки? (Обычно нет, но если да — их нужно тримить).
- Разрешена ли научная нотация (
1e-5,2E10)? Это значительно усложнит автомат. - Как обрабатывать ведущие нули (
0012)? Обычно они недопустимы для целых чисел. - Разрешены ли числа, начинающиеся с точки (
.5)? В нашем решении они разрешены. - Можно ли использовать встроенные функции или регулярные выражения? Как правило, нет.
Заключение
Реализация через конечный автомат — это профессиональный, масштабируемый и надежный подход. Он четко разделяет логику, легко модифицируется при изменении требований (например, добавление поддержки научной нотации) и демонстрирует глубокое понимание процесса разбора данных. Для QA Automation Engineer важно не только написать работающий код, но и понимать его логику, уметь составлять тестовые сценарии, покрывающие все состояния и переходы (например, таблица принятия решений или техника классов эквивалентности и граничных значений).