Понимаете, что это за код Erlang?

Не знаком с Erlang, но пытаюсь понять, что делает этот код?

Ниже я расскажу о коде. Любая помощь будет полезна. Я смотрю учебные пособия, но передаваемые значения в этом случае сбивают с толку.

пример- convert_list_to_k([{Name, {l, Weight}} | Rest]) //{1,Weight} <- This one

А как значение возвращается в convert_list_to_k?

скажем, для этого функционального блока

convert_list_to_k([{Name, {l, Weight}} | Rest]) ->
    Converted_Object = {Name, {k, Weight / 0.45359237}},
    [Converted_Object | convert_list_to_k(Rest)];

convert_list_to_k([Object | Rest]) ->
    [Object | convert_list_to_k(Rest)];

convert_list_to_k([]) ->
    [].

Ниже приведен код с пояснениями.

-module(erlang_program).
-export([format_weight/1]).

в приведенном выше экспорте / 1 означает, что он получит атрибут (я не знаю, какой атрибут)

format_weight(List_of_objects) ->
    Converted_List = convert_list_to_k(List_of_objects),
    print_weight(Converted_List),
    {Max_object, Min_object} = find_max_and_min(Converted_List),
    print_max_and_min(Max_object, Min_object).

Вид основной функции, которая импортирует convert_list_to_k, _7 _, _ 8_ и print_max_and_min(Max_object, Min_object).

Насколько я понимаю, он выполняет следующие действия:

  1. Преобразует список объектов в некоторый формат
  2. Печатает преобразованный список
  3. Найдите Max и Min и поместите их в Object Max и Min.
  4. Печатает объект Max и Min

Я запутался, когда передано [{Name, {l, Weight}} | Rest]

convert_list_to_k([{Name, {l, Weight}} | Rest]) ->
    Converted_Object = {Name, {k, Weight / 0.45359237}},
    [Converted_Object | convert_list_to_k(Rest)];

convert_list_to_k([Object | Rest]) ->
    [Object | convert_list_to_k(Rest)];

convert_list_to_k([]) ->
    [].

print_weight([{Name, {k, Weight}} | Rest]) ->
    io:format("~-15w ~w c~n", [Name, Weight]),
    print_weight(Rest);

print_weight([]) ->
    ok.

find_max_and_min([Object | Rest]) ->
    find_max_and_min(Rest, Object, Object).

find_max_and_min([{Name, {k, Weight}} | Rest], 
         {Max_Name, {k, Max_Weight}}, 
         {Min_Name, {k, Min_Weight}}) ->
    if 
        Weight > Max_Weight ->
            Max_Object = {Name, {k, Weight}};
        true -> 
            Max_Object = {Max_Name, {k, Max_Weight}}
    end,
    if
         Weight < Min_Weight ->
            Min_Object = {Name, {k, Weight}};
        true -> 
            Min_Object = {Min_Name, {k, Min_Weight}}
    end,
    find_max_and_min(Rest, Max_Object, Min_Object);

find_max_and_min([], Max_Object, Min_Object) ->
    {Max_Object, Min_Object}.

print_max_and_min({Max_name, {k, Max_object}}, {Min_name, {k, Min_object}}) ->
    io:format("Max weight was ~w c in ~w~n", [Max_object, Max_name]),
    io:format("Min weight was ~w c in ~w~n", [Min_object, Min_name]).

person hemantmetal    schedule 28.10.2017    source источник
comment
это просто кортеж   -  person    schedule 28.10.2017
comment
convert_list_to_k([{Name, {l, Weight}} | Rest]) - это такой цикл for, который вычитает 1 из Rest и может что-то делать?   -  person hemantmetal    schedule 28.10.2017
comment
Это преобразование из фунтов в килограммы (фунты в кг).   -  person svujic    schedule 29.10.2017
comment
И, в конце концов, он получает (печатает) максимальный вес и минимальный вес, правильно?   -  person hemantmetal    schedule 29.10.2017
comment
По какой-то причине я пытаюсь вставить его в IDE, чтобы запустить код, но не могу. Возможно ли это сделать в IDE tutorialspoint?   -  person hemantmetal    schedule 29.10.2017


Ответы (1)


Не волнуйтесь, что этот код немного сбивает с толку. Это несколько унидиоматично. Мы рассмотрим это через мгновение ...

Перед стилем взгляните на эту первую функцию convert_list_to_k/1. Это выборочное преобразование объектов из формы, отмеченной l, в форму, отмеченную k.

Как он выбирается? Это соответствие по форме и значению первого элемента списка, переданного ему в качестве аргумента. Если он получает значение с типом l внутри типа {Name, {l, Weight}}, то выбирается и запускается первое предложение, которое преобразует часть {l, Weight} в значение {k, Weight} - я предполагаю, что здесь это «l» для «фунтов» и «k» для «килограммы».

Эта функция выполняет рекурсию по глубине, что обычно не подходит для этого конкретного случая, потому что Erlang (и большинство функциональных языков) оптимизированы для хвостовой рекурсии.

foo([Thing | Things]) ->
    NewThing = change(Thing),
    [NewThing | foo(Things)];
foo([]) ->
    [].

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

Представьте себе, что в памяти вы видите это:

foo([change(Thing1) | foo([change(Thing2) | foo([change(Thing3) | ...]]])

Не очень аккуратно. Иногда это правильно, но не в общем случае итерации по списку.

Хвостовая рекурсивная версия будет выглядеть так:

foo(Things) ->
    foo(Things, []).

foo([Thing | Things], Accumulator) ->
    NewThing = change(Thing),
    foo(Things, [NewThing | Accumulator]);
foo([], Accumulator) ->
    lists:reverse(Accumulator).

Эта версия работает в постоянном пространстве и является более идиоматической формой явной рекурсии.

Так что насчет всего этого соответствия? Что ж, допустим, я хотел каждый раз печатать значение в килограммах, но некоторые из моих значений указаны в фунтах, а некоторые - в килограммах. Я мог бы обернуть необработанные числовые значения в кортеж и использовать атом для тегирования значений, чтобы я знал, что они означают. Например, такой кортеж, как {pounds, X}, будет означать, что у меня есть число X, и оно выражено в фунтах, или кортеж {kilos, X}, который будет означать, что X - это килограммы. Оба по-прежнему в весе.

Так как бы выглядела моя функция?

print_weight({kilos, X}) ->
    io:format("Weight is ~wkgs~n", [X]);
print_weight({pounds, X}) ->
    Kilos = X / 0.45359237,
    io:format("Weight is ~wkgs~n", [Kilos]).

Таким образом, эта функция работает нормально, если ей передается кортеж любого типа.

Как насчет их списка? Мы могли бы сделать явную рекурсию, как указано выше:

print_weights([{kilos, X} | Rest]) ->
    ok = io:format("Weight is ~wkgs~n", [X]),
    print_weights(Rest);
print_weight([{pounds, X} | Rest]) ->
    Kilos = X / 0.45359237,
    ok = io:format("Weight is ~wkgs~n", [Kilos]),
    print_weights(Rest);
print_weights([]) ->
    ok.

Итак, это обрабатывает список значений, как указано выше. Но нам действительно не нужно все это писать, не так ли? У нас уже была функция под названием print_weight/1, и она уже знает, как выполнять сопоставление. Вместо этого мы могли бы проще определить print_weights/1 как функцию, которая использует операцию со списком:

print_weights(List) ->
    lists:foreach(fun print_weight/1, List).

Видите ли, мы обычно не делаем явную рекурсию, когда можем с этим помочь. Причина в том, что в простом случае у нас уже есть функции высшего порядка. сделано для упрощения простого перебора списков. В случае, когда нам нужен побочный эффект и мы не заботимся о возвращаемом значении, например, при выводе весов, как указано выше, мы используем _ 18_.

Возвращаясь к приведенному выше примеру «изменения», если мы уже знаем, что хотим выполнить change/1 для каждого значения, но вернуть ту же карту обратно в неизменном виде, имеет смысл использовать либо понимание списка или _ 20_.

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

ChangedThings = [change(Thing) || Thing <- Things]

Карта выглядит почти так же, как lists:foreach/2 выше:

ChangedThings = lists:map(fun change/1, Things)

Теперь, возвращаясь к исходному примеру ... может быть, мы хотим обеспечить определенный тип значения. Итак, мы могли бы написать простую функцию, которая делает только это:

ensure_metric({Name, {l, Pounds}}) ->
    Kilos = Pounds / 0.45359237,
    {Name, {k, Kilos}};
ensure_metric(Value = {_, {k, _}}) ->
    Value.

Это все, что нам нужно. Что происходит выше, так это то, что любой кортеж формы {Foo, {l, Bar}} соответствует первому предложению и преобразуется операцией в этом предложении, а затем переупаковывается в форму {Foo, {k, Baz}, а любой кортеж формы {Foo, {k, Bar}} совпадает со вторым, но передается без изменения . Теперь мы можем просто сопоставить эту функцию со списком:

convert_list_to_k(List) ->
    lists:map(fun ensure_metric/1, List).

Намного проще рассуждать только об одной функции за раз!

Функция min / max немного безумна. Мы не хотели бы писать if, если бы у нас не был полностью ограниченный математический случай. Например:

if
    X >   Y -> option1();
    X =:= Y -> option2();
    X ==  Y -> option3();
    X <   Y -> option4()
end,

Это четыре теста в одном предложении. Иногда использование if имеет для этого смысл. Однако чаще вы получаете то, что у вас было выше, где происходит простое сравнение. В этом случае case гораздо выразительнее:

case X > Y ->
    true  -> do_something();
    false -> something_else()
end,

НО! Может быть, то, что мы действительно хотим в функции min / max, - это просто управлять охранниками и избегать написания сложной логики тела. Вот тот, который работает с простым списком чисел, небольшое изменение приведет к тому, что он будет соответствовать типу данных, с которым вы имеете дело (эти кортежи):

min_max([Number | Numbers]) ->
    min_max(Numbers, Number, Number).

min_max([N | Ns], Min, Max) when N < Min ->
    min_max(Ns, N, Max);
min_max([N | Ns], Min, Max) when N > Max ->
    min_max(Ns, Min, N);
min_max([_ | Ns], Min, Max) ->
    min_max(Ns, Min, Max);
min_max([], Min, Max) ->
    {Min, Max}.

В процедурной логике здесь не требуется много подбрасываний гепарда.

Эрланг настолько скучно прост и крошечный, как язык, что, как только ненужность большей части процедурной логики проникает в вас, вы просто внезапно "получаете новые глаза". Несколько связанных вопросов и ответов со справочной информацией могут быть полезны в вашем путешествии:

person zxq9    schedule 29.10.2017
comment
Ваша хвостовая рекурсивная версия не работает в постоянном пространстве, потому что Accumulator занимает пространство O (N), а lists:reverse/1 снова займет такое же количество пространства. Пожалуйста, не распространяйте Миф: хвостовые рекурсивные функции намного быстрее, чем рекурсивные. - person Hynek -Pichi- Vychodil; 29.10.2017
comment
@ Hynek-Pichi-Vychodil Я ничего не говорил о скорости. Только пространство. Накопитель накапливает с той же скоростью, что и список ввода в типичной операции хвостовой рекурсии. Это неверно для версии с глубиной рекурсии, где пространство потребляется с значительно скоростью, поскольку стек вызовов увеличивается с частичными копиями список ввода. Я ничего не сказал о скорости и также упомянул, что иногда рекурсия глубины является подходящим решением. Этот код был унидиоматическим, и OP - новичок, который, вероятно, мало что знает об алгоритмах обхода в глубину. - person zxq9; 29.10.2017
comment
@ Hynek-Pichi-Vychodil Также относительно lists:reverse/1: эта конкретная функция сильно оптимизирована, являясь идиоматическим вызовом в большинстве программ Erlang, и реализована на C с использованием арифметики указателей (на самом деле это BIF). Накапливает ли он вообще какой-либо мусор - это деталь реализации, зависящая от сокращений, присущих выполняющемуся процессу до выполнения, и не гарантируется удвоение пространства в каждом случае. Еще раз, это то, что следует использовать в типичном случае, потому что он поддерживает идиоматическое использование языка, что является основным пунктом выше. - person zxq9; 29.10.2017
comment
Я должен называть вашу информацию чушью. Прямая рекурсивная или глубинно-рекурсивная версия не только быстрее, но и потребляет меньше памяти! Вы можете увидеть себя: gist.github.com/pichi/d09c8441e9c52a3181bcae7 размышлять! Измерьте! И снова, пожалуйста, не распространяйте Миф: хвостовые рекурсивные функции намного быстрее, чем рекурсивные функции. - person Hynek -Pichi- Vychodil; 29.10.2017
comment
Комментарии совершенно мне чужды. Но определенно этот ответ дополняет мои знания. Большое Вам спасибо. - person hemantmetal; 29.10.2017
comment
@ Гинек-Пичи-Выходил Да, измерить. Измеряйте во время выполнения, а не после того, как мы не знаем, что сделал GC. Кроме того, общая куча - не единственное интересное число, особенно с учетом размера хранимого стека. gist.github.com/zxq9/c28b2a35c76c4a1e987fc1497a45cc58 Проблема становится более драматичной на итерацию (вы можете немного увидеть этот эффект с строковой версией тестовой функции, но это все еще стандартный пример). Интересно наблюдать, как обе версии развиваются по мере выполнения сборок мусора. В любом случае, я имел в виду идиомы, а не производительность. - person zxq9; 30.10.2017
comment
Вы не знаете, что стек разделяет память с кучей, поэтому total_heap уже содержит стек. РЖУ НЕ МОГУ. Итак, если вы посмотрите на ваше заведомо несправедливое сравнение, прямая рекурсия заняла 16К слов, а хвостовая рекурсия - 12К слов. Это так значительно выше :-D Сколько времени у вас ушло на эту жалкую попытку? - person Hynek -Pichi- Vychodil; 30.10.2017
comment
Если вас интересуют идиомы, сравните хвостовую рекурсивную версию и прямую рекурсивную версию. Прямая версия короче, быстрее и проще для понимания. Если вам нужно вызвать lists:reverse/1, чтобы получить хвостовую рекурсивную версию, просто не делайте этого. Это ерунда. Это затрудняет чтение кода, и оно того не стоит. Так обстоит дело с последними четырьмя или пятью версиями Erlang. Не распространяйте этот миф о хвостовой рекурсии. Если вам нужен lists:reverse/1 компилятор Erlang, без него можно будет неплохо справиться. - person Hynek -Pichi- Vychodil; 30.10.2017
comment
@ Hynek-Pichi-Vychodil Ясно, что суперзвезда каждой команды, которую можно украсить своим блеском. - person zxq9; 30.10.2017