🟨 JavaScript
Средняя
🕐 15 мин

Группировка чисел в диапазоны

Цель: попрактиковаться в обработке массивов, сортировке и группировке данных.

💡 Подсказка к решению
  1. Первым делом, отсортируйте массив чисел по возрастанию.
  2. Чтобы обработать дубликаты, можно создать Set из массива, а затем снова преобразовать его в массив.
  3. Итерируйтесь по отсортированному массиву. Вам понадобится переменная для хранения начала текущего диапазона.
  4. На каждой итерации проверяйте, является ли следующее число в массиве последовательным (т.е. current + 1).
  5. Если последовательность прерывается (или вы дошли до конца массива), текущий диапазон завершен.
  6. Если начало и конец диапазона совпадают, это одиночное число. Если нет — это диапазон start-end.
  7. Собирайте полученные строки (одиночные числа и диапазоны) в массив, а в конце соедините их через запятую.
  8. Не забудьте обработать случай с пустым входным массивом.
👀 Решение
const range = (numbers) => {
  // Шаг 1: Обработка пустого массива.
  // Если массив пуст, по условию возвращаем пустую строку.
  if (numbers.length === 0) return '';
 
  // Шаг 2: Подготовка данных.
  // Создаем `Set` из массива, чтобы автоматически удалить дубликаты.
  // `Array.from` преобразует `Set` обратно в массив.
  // `.sort((a, b) => a - b)` сортирует числа в порядке возрастания.
  const sortedUniqNumbers = Array.from(new Set(numbers))
    .sort((a, b) => a - b);
  
  // Шаг 3: Инициализация переменных.
  // `ranges` — массив для хранения итоговых строк (чисел и диапазонов).
  const ranges = [];
  // `start` — хранит начальное число текущего диапазона.
  let start = sortedUniqNumbers[0];
  // `prev` — хранит предыдущее число для проверки последовательности.
  let prev = start;
 
  // Шаг 4: Итерация по массиву для поиска диапазонов.
  // Начинаем со второго элемента (i=1) и идем до конца массива (включая "виртуальный" элемент после последнего).
  // Это позволяет обработать последний диапазон без дополнительного кода после цикла.
  for (let i = 1; i <= sortedUniqNumbers.length; i++) {
    // `current` — текущее рассматриваемое число.
    const current = sortedUniqNumbers[i];
    
    // Шаг 5: Проверка, продолжается ли последовательность.
    // Если текущее число на 1 больше предыдущего, значит, мы все еще в диапазоне.
    if (current === prev + 1) {
      // Обновляем `prev` и переходим к следующему числу.
      prev = current;
      continue;
    }
    
    // Шаг 6: Последовательность прервалась. Формируем строку для завершенного диапазона.
    // Если `start` и `prev` равны, диапазон состоял из одного числа.
    if (start === prev) {
      ranges.push(String(start));
    } 
    // Если в диапазоне было два числа (например, 5, 6), они не образуют диапазон "5-6".
    // Добавляем их как два отдельных числа.
    else if (start + 1 === prev) {
      ranges.push(String(start), String(prev));
    } 
    // Если чисел в диапазоне три или больше, форматируем как `start-prev`.
    else {
      ranges.push(`${start}-${prev}`);
    }
    
    // Шаг 7: Сброс переменных для следующего потенциального диапазона.
    // Новое начало — это `current`, число, которое прервало предыдущую последовательность.
    start = current;
    prev = current;
  }
 
  // Шаг 8: Возвращаем результат.
  // Объединяем все строки из массива `ranges` в одну, разделенную запятыми.
  return ranges.join(',');
};

Анализ решения:

Этот подход к решению задачи можно разбить на несколько ключевых этапов:

  1. Обработка крайнего случая: Первым делом функция проверяет, не является ли входной массив пустым. Если это так, она немедленно возвращает пустую строку в соответствии с требованиями задачи. Это простая, но важная оптимизация, которая предотвращает выполнение лишнего кода.

  2. Подготовка данных (Очистка и Сортировка):

    • Удаление дубликатов: Входные данные могут содержать повторяющиеся числа. Чтобы обработать их как одно, мы используем структуру данных Set, которая по своей природе хранит только уникальные значения. Создание new Set(numbers) и последующее преобразование обратно в массив с помощью Array.from() является элегантным и эффективным способом избавиться от дубликатов.
    • Сортировка: Для того чтобы находить последовательные числа, массив должен быть отсортирован. Метод .sort((a, b) => a - b) гарантирует, что числа будут расположены в порядке возрастания, что является необходимым условием для нашего алгоритма.
  3. Итеративный поиск диапазонов:

    • Инициализация: Мы заводим три переменные: ranges для хранения итоговых строк, start для отметки начала текущего диапазона и prev для хранения предыдущего элемента.
    • Цикл: Мы итерируемся по отсортированному массиву, начиная со второго элемента. Ключевая идея здесь — сравнивать текущий элемент (current) с предыдущим (prev).
    • Логика определения диапазона:
      • Если current на единицу больше prev, это означает, что последовательность продолжается. Мы просто обновляем prev и переходим к следующей итерации.
      • Если последовательность прерывается (числа не идут подряд) или мы дошли до конца массива, нам нужно “закрыть” текущий диапазон.
    • Форматирование вывода: При закрытии диапазона мы проверяем:
      • Если start и prev равны, значит, это было одиночное число.
      • Если в диапазоне было всего два числа (start + 1 === prev), мы добавляем их по отдельности, а не как диапазон (например, 5,6 вместо 5-6).
      • Если в диапазоне было три и более чисел, мы форматируем его как start-prev.
  4. Сборка результата: После завершения цикла массив ranges содержит все необходимые строки ('0-5', '8', '9', '11'). Метод join(',') объединяет их в финальную строку, которую и возвращает функция.

  • Сложность по времени: O(N log N), где N — количество элементов в массиве. Основным узким местом является сортировка (.sort()). Сам цикл проходит по массиву один раз, что дает O(N), но сортировка доминирует.
  • Сложность по памяти: O(N) для хранения отсортированного массива без дубликатов и массива ranges с результатами. В худшем случае (когда нет диапазонов) оба массива будут иметь размер, сравнимый с размером исходного массива.

Описание задачи

Напишите функцию range, которая принимает массив целых чисел и возвращает строку, представляющую отсортированные и сгруппированные диапазоны этих чисел.

Числа в строке должны быть отсортированы по возрастанию. Последовательные числа должны быть сгруппированы в диапазон, например, 1, 2, 3, 4 превращается в 1-4. Если число не входит ни в какой диапазон, оно остается как есть.

Примеры

range([1, 4, 5, 2, 3, 9, 8, 11, 0]);
// Вернет: '0-5,8,9,11'
 
range([-1, 0, 1, 5, 6]);
// Вернет: '-1-1,5,6'
 
range([1, 3, 5]);
// Вернет: '1,3,5'

Требования

  • Функция должна называться range.
  • Она должна принимать массив чисел.
  • Она должна возвращать строку.
  • Входной массив может содержать дубликаты, которые следует обрабатывать как одно число.
  • Массив может быть не отсортирован.
  • Если входной массив пуст, функция должна вернуть пустую строку.

🧑‍💻 Это не баг! Это фича!

Редактор кода намеренно скрыт на мобильном.

Поверь, так лучше: я оберегаю тебя от искушения писать код в неидеальных условиях. Маленький экран и виртуальная клавиатура — не лучшие помощники для программиста.

📖 Сейчас: Изучи задачу, продумай решение. Действуй как стратег.

💻 Потом: Сядь за компьютер, открой сайт и реализуй все идеи с комфортом. Действуй как код-джедай!