Unfold и выделение цифр из целого числа
Как выделить цифры из целого числа?
Все знают простой способ: преобразовать целое в строку и затем каждый символ строки назад преобразовать в цифру. Вот решение в виде функции:
function Digits0(a: integer): sequence of integer; begin Result := a.ToString.Select(c -> c.ToDigit); end; begin var n := 234635681; Digits0(n).Print end.
Но такой способ крайне непроизводителен - преобразование числа в строку и назад - крайне ресурсоёмкая операция.
С другой стороны, известен простой способ - выделение цифр с конца:
procedure Digits1(n: integer); begin while n<>0 do begin Print(n mod 10); n := n div 10; end; end; begin var n := 234635681; Digits1(n) end.
Увы - это процедура, и она просто выводит цифры.
Превратим ее в функцию, сохраняя цифры в списке:
function Digits2(n: integer): sequence of integer; begin var L := new List<integer>; while n<>0 do begin L.Add(n mod 10); n := n div 10; end; Result := L; end;
Такой код существенно лучше, но тратится память под список.
Обойдёмся последовательностями. Здесь нам на помощь приходят функции-генераторы последовательностей. Ключевое здесь - использование оператора yield, выбрасывающего следующий член последовательности:
function Digits3(n: integer): sequence of integer; begin while n<>0 do begin yield n mod 10; n := n div 10; end; end;
Можно даже написать рекурсивную версию данной функции:
function Digits4(n: integer): sequence of integer; begin if n = 0 then exit; yield sequence Digits4(n div 10); yield n mod 10; end;
Здесь используется оператор yield sequence, генерирующий подпоследовательность в рамках функции-генератора последовательности. Логика данной функции предельно проста: вначале генерируем подпоследовательность из всех цифр кроме последней, а затем добавляем к ней последнюю цифру. Кстати, этот способ позволяет сразу инвертировать последовательность цифр, записав их в порядке слева направо.
while n<>0 do begin Print(n mod 10); n := n div 10; end;
Здесь есть три основных элемента: условие продолжения цикла n<>0, генерируемый элемент последовательности n mod 10 и переход к следующему числу без последней цифры n div 10.
Всё это - элементы функции Unfold, преобразующей скаляр (в данном случае целое число) в последовательность. Приведём её реализацию:
function Unfold<T,TRes> (Self: T; generator: T -> (TRes,T)): sequence of TRes; extensionmethod; begin var next := Self; while True do begin var res := generator(next); if res = nil then break; yield res[0]; next := res[1]; end; end;
n.Unfold(n -> n = 0 ? nil : (n mod 10, n div 10)).Println;
Это самый короткий универсальный способ преобразования скаляра в последовательность. Здесь n = 0 возвращает nil - признак конца последовательности, а иначе возвращается кортеж из двух элементов: член генерируемой последовательности n mod 10 и число n div 10 без последней цифры.
Если написать еще одну элементарную функцию-негенератор
function Iterate<T>(first: T; next: T->T): sequence of T; begin yield first; while True do begin first := next(first); yield first; end; end;
которая генерирует бесконечную рекуррентную последовательность с функцией next перехода к следующему элементу, то можно представить алгоритм выделения цифр из числа цепочкой методов - как мы любим:
var digits := Iterate(n,n -> n div 10) .TakeWhile(n -> n <> 0) .Select(n -> n mod 10);
Здесь налицо и переход к следующему числу n div 10 и условие продолжения n<>0 и выделение последней цифры n mod 10
Заключение
Существует много сложных способов выделения цифр из целого числа :)
А если серьёзно - Unfold и Iterate - базовые примитивы для последовательностей, которые можно использовать не только здесь, но и в других задачах.