Работа со списками и значениями списка в Erlang

Я изучаю Erlang и делаю упражнения из книги, поэтому я застрял на одном из них. Будет лучше, если я процитирую всю задачу, а затем объясню, что я сделал до сих пор: «Положительное число считается счастливым, если при повторном применении приведенной ниже процедуры достигается число 1. 1. Возведите в квадрат каждую цифру числа. 2. Вычислите сумму всех квадратов. Например, если вы начинаете с 19:

 1 * 1 + 9 * 9 = 1 + 81 = 82
 8 * 8 + 2 * 2 = 64 + 4 = 68
 6 * 6 + 8 * 8 = 36 + 64 = 100
 1 * 1 + 0 * 0 + 0 * 0 = 1 + 0 + 0 = 1 

(т. е. 19 — счастливое число) Как узнать, что число несчастливое? На самом деле, каждое несчастливое число в конце концов достигнет цикла 4, 16, 37, 58, 89, 145, 42, 20, 4, … таким образом, достаточно найти любое число в этом цикле (скажем, 4) и сделать вывод, что исходный номер несчастлив. Напишите функции happy/1 и all_happy/2, которые возвращают, является ли число счастливым или нет (истинным или ложным), и все счастливые числа между N и M соответственно. (Подсказка: используйте функции оцифровки и суммирования). Примеры:

 happy(28) → true
 happy(15) → false
 happy(5, 25) → [7, 10, 13, 19, 23]"

Итак, я создал дигитайзер/1, который при положительном числе N возвращает список цифр этого числа:

digitize(N) -> digitize1(N, []).
digitize1(N, Acc) when N > 0 -> digitize1(N div 10, [N rem 10| Acc]);
digitize1(N, Acc) when N == 0 -> Acc.

и сумма/1:

sum(N) when  N > 0 -> N + sum(N-1);
sum(0) ->   0.

Итак, для счастливых чисел я сделал следующее:

happy(N) -> happy1(digitize(N), []).
happy1([], Acc) -> (Acc);
happy1([Head|Tail], Acc1) -> happy1(Tail, [Head * Head|Acc1]).

Он возводит элементы списка в квадрат, но я не могу придумать, как суммировать их и делать это снова рекурсивно, пока не достигнет 1 или 4. Любая помощь или идеи? А для второй части (all_happy/2), по моему некомпетентному мнению, я должен использовать понимание списка, но опять же, я не совсем уверен, как это реализовать. Спасибо за ваше время.


person G.M    schedule 24.09.2013    source источник
comment
Это классное упражнение :) Не знаю, знаете ли вы проект Euler... Если нет, то вы найдете множество других подобных небольших упражнений для тренировки erlang (или любого другого языка).   -  person lud    schedule 25.09.2013


Ответы (2)


Одна вещь, которую я заметил, заключается в том, что ваш цикл happy1 может просто вычислить сумму напрямую, вам не нужно составлять список, а затем добавлять их:

calculate([], Total)->
  Total;
calculate([First | Rest], Total) ->
  calculate(Rest, Total + (First * First)).

Что касается основного вопроса вашего вопроса, вы можете использовать сопоставление с образцом, чтобы определить, достигли ли вы несчастливого числа или достигли ли вы 1.

У меня есть работающая реализация, но я предполагаю, что вы хотели бы выяснить детали для себя. Дайте мне знать, если вы хотите, чтобы я опубликовал его.


Вот мое решение:

-module(happy).

-export([happy/1]).

happy(1) ->
  happy;
happy(4) ->
  not_happy;
happy(Num) ->
  io:format("Current loop: ~p~n", [Num]),
  Digits = digitize(Num),
  happy(calculate(Digits, 0)).


digitize(N) -> digitize1(N, []).
digitize1(N, Acc) when N > 0 -> digitize1(N div 10, [N rem 10| Acc]);
digitize1(N, Acc) when N == 0 -> Acc.

calculate([], Total)->
  Total;
calculate([First | Rest], Total) ->
  calculate(Rest, Total + (First * First)).

Выход:

3> happy:happy(55).
Current loop: 55
Current loop: 50
Current loop: 25
Current loop: 29
Current loop: 85
Current loop: 89
Current loop: 145
Current loop: 42
Current loop: 20
not_happy
4> happy:happy(4). 
not_happy
5> happy:happy(19).
Current loop: 19
Current loop: 82
Current loop: 68
Current loop: 100
happy
6> happy:happy(20).
Current loop: 20
not_happy
7> happy:happy(21).
Current loop: 21
Current loop: 5
Current loop: 25
Current loop: 29
Current loop: 85
Current loop: 89
Current loop: 145
Current loop: 42
Current loop: 20
not_happy

Если вам интересно, как использовать понимание списка, вот основное предложение, которое пропускает метод вычисления и использует функцию lists:sum со списком построения:

happy(Num) ->
  io:format("Current loop: ~p~n", [Num]),
  Digits = [ X * X || X <- digitize(Num)],
  happy(lists:sum(Digits)).
person kjw0188    schedule 24.09.2013
comment
Спасибо за вашу помощь. На самом деле я хотел бы увидеть ваше решение этой проблемы, так как я перепробовал все, что мог, и яm at the point when I just want to see and understand how itсделал. Так что да, пожалуйста, опубликуйте его, если можете. Спасибо еще раз. - person G.M; 25.09.2013
comment
Я отредактировал свой ответ. У меня были некоторые неточности в исходном сообщении; оказывается, я неправильно понял вопрос. Теперь должно быть правильно, дайте мне знать, если что-то нуждается в разъяснении. - person kjw0188; 25.09.2013
comment
Большое спасибо, это не может быть более ясным! Я бы проголосовал за ваш ответ N раз, если это возможно! :) Понимание кажется удивительно легким, когда оно написано и подано. - person G.M; 25.09.2013

Другое решение, которое реализуется через хвостовую рекурсию:

digitize(N) -> digitize1(N, [],N).
digitize1(N, Acc,M) when N > 0 -> digitize1(N div 10, [N rem 10| Acc],M);
digitize1(N, Acc,M) when N == 0 -> sum_digits(Acc,0,M).

sum_digits([],Acc,N) when Acc == 1 -> {happy,N,Acc};
sum_digits([], Acc,N) when Acc == 4 -> {unhappy,N,Acc};
sum_digits([],Acc,N) -> {digitize,Acc}, digitize1(Acc,[],N);
sum_digits([H|T],Acc,N)-> sum_digits(T,H*H+Acc,N).

использовать:

1> c('test.erl').
test.erl:15: Warning: a term is constructed, but never used
{ok,test}
2> c('test.erl').
test.erl:15: Warning: a term is constructed, but never used
{ok,test}
3> c('test.erl').
test.erl:15: Warning: a term is constructed, but never used
{ok,test}
4> test:digitize(55).
{unhappy,55,4}
5> test:digitize(19).
{happy,19,1}
6> test:digitize(5). 
{unhappy,5,4}
7> test:digitize(20).
{unhappy,20,4}
8> test:digitize(21).
{unhappy,21,4}
person user462003    schedule 12.11.2014