IntRangeFormatter преобразует последовательность целых чисел в нормализованную строку диапазонов.
Публичный API:
public static class IntRangeFormatter
{
public static Result<string> Format(IEnumerable<int>? values, string? separator = ",");
public static Result<string> Format(
IEnumerable<int>? values,
int maxRangeValue,
string? separator = ",",
bool useOpenRanges = true);
}Методы не используют исключения для ошибок пользовательского ввода.
Ошибки валидации возвращаются через Result<string>.
Formatter принимает произвольную последовательность целых чисел и:
- валидирует значения;
- при необходимости сортирует их по возрастанию;
- удаляет дубликаты;
- объединяет соседние значения в диапазоны;
- возвращает строковое представление диапазонов.
Результат всегда нормализован:
- значения идут по возрастанию;
- дубликаты не повторяются;
- соседние значения склеиваются в диапазоны.
Методы принимают любые последовательности, реализующие IEnumerable<int>.
Например:
List<int>int[]HashSet<int>SortedSet<int>- LINQ-последовательности
yield-последовательности
Входная последовательность не обязана быть:
- отсортированной;
- уникальной;
- материализованной заранее.
Если диапазон состоит из одного значения, оно записывается как:
N
Примеры:
[5]->"5"[0]->"0"
Если есть непрерывная последовательность значений, она записывается как:
N-M
Примеры:
[1,2,3]->"1-3"[0,1,2,3]->"0-3"[7,8,9,10]->"7-10"
Result<string> Format(IEnumerable<int>? values, string? separator = ",")Эта перегрузка не использует открытые диапазоны. Она всегда форматирует диапазоны только в явном виде:
NN-M
Примеры:
[1,2,3,5,7,8,9]->"1-3,5,7-9"[0,1,2,3]->"0-3"
Result<string> Format(
IEnumerable<int>? values,
int maxRangeValue,
string? separator = ",",
bool useOpenRanges = true)Если useOpenRanges == true, formatter может использовать открытые диапазоны:
-NN-0-
Используется только для диапазона:
1..N
Примеры:
[1,2,3,4,5],maxRangeValue = 10->"-5"[1],maxRangeValue = 10->"1"
(-1теоретически допустим, но одиночное значение formatter не превращает в диапазон)
Используется для диапазона:
N..maxRangeValue
Примеры:
[10,11,12],maxRangeValue = 12->"10-"[0,1,2,3],maxRangeValue = 3->"0-"
Используется для диапазона:
0..maxRangeValue
Пример:
[0,1,2,3],maxRangeValue = 3->"0-"
Formatter не использует -N для диапазона 0..N, потому что по контракту IntRangeParser
запись -N означает именно 1..N.
То есть:
[0,1,2,3]->"0-3"или"0-"при известномmaxRangeValue- но не
"-3"
Параметр separator задаёт строку, которая используется между токенами результата.
Примеры допустимых разделителей:
","", "";"" "" | "
Если нужен гарантированный round-trip с IntRangeParser, separator должен состоять
только из символов, поддерживаемых парсером как разделители:
" ,.;_:#!|\\/'\""Иначе formatter возвращает Failure.
Возвращается Failure.
Возвращается Failure.
Возвращается Failure.
Любое значение < 0 недопустимо.
Возвращается Failure.
Дополнительно:
maxRangeValue < 0->Failure- любое значение
> maxRangeValue->Failure
Перед форматированием последовательность нормализуется.
Если вход не отсортирован, formatter сортирует значения по возрастанию.
Пример:
[7,3,2,1,8,9,5]->"1-3,5,7-9"
Если во входе есть повторы, они удаляются.
Пример:
[1,2,2,3,3,3,5]->"1-3,5"
Format(new[] { 1, 2, 3, 5, 7, 8, 9 })Результат:
"1-3,5,7-9"Format(new[] { 9, 8, 7, 5, 3, 2, 1 })Результат:
"1-3,5,7-9"Format(new[] { 1, 2, 2, 3, 5, 5, 7 })Результат:
"1-3,5,7"Format(new[] { 0, 1, 2, 3 })Результат:
"0-3"Format(new[] { 1, 2, 3, 4, 5 }, maxRangeValue: 10)Результат:
"-5"Format(new[] { 10, 11, 12 }, maxRangeValue: 12)Результат:
"10-"Format(new[] { 0, 1, 2, 3 }, maxRangeValue: 3)Результат:
"0-"Для корректного round-trip должны выполняться условия:
- использовать совместимый
separator; - использовать одинаковый
maxRangeValue, если включены открытые диапазоны; - учитывать, что formatter нормализует вход (сортировка + удаление дублей).
Пример:
var formatted = IntRangeFormatter.Format(new[] { 7, 3, 2, 1, 3, 8, 9, 5 }, ", ");
var parsed = IntRangeParser.Parse(formatted.Value, maxRangeValue: 100);После round-trip результат будет:
[1,2,3,5,7,8,9]Formatter не использует bitset-подобные структуры. Для этой задачи достаточно:
- собрать вход в список;
- выполнить сортировку;
- удалить дубли;
- собрать диапазоны за один линейный проход;
- сформировать строку через
StringBuilder.
Такой алгоритм хорошо подходит как для обычных коллекций, так и для потоковых IEnumerable<int>.