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 - базовые примитивы для последовательностей, которые можно использовать не только здесь, но и в других задачах.