Enum.reduce reruning: нормально после первой итерации - PullRequest
1 голос
/ 26 марта 2019

Я передаю список, состоящий из целых чисел в виде строк в Enum.reduce, и запускаю функцию для каждого значения списка.Но проблема во второй итерации:мы передаем «11» в качестве параметра, тогда он должен вернуть десятичное представление этого двоичного значения.Но это останавливается на второй итерации, я напишу вывод ниже -

def to_decimal(string) do
    list = String.graphemes(string)
    len = length(list)
    Enum.reduce(list,0, fn x,acc ->
            temp=String.to_integer(x)
            power = round(:math.pow(2,len-1))
            IO.puts(power)
            acc = acc + temp*power
            IO.puts(acc)
            len=len-1
            IO.puts(len)
            end)
end

iex(1)> Binary.to_decimal("11")
2
2
1
2
** (ArithmeticError) bad argument in arithmetic expression: :ok + 2
    :erlang.+(:ok, 2)
    binary.exs:16: anonymous fn/3 in Binary.to_decimal/1
    (elixir) lib/enum.ex:1925: Enum."-reduce/3-lists^foldl/2-0-"/3

Ответы [ 3 ]

3 голосов
/ 26 марта 2019

Для реального кода вы должны использовать заранее подготовленное решение:

String.to_integer(string, 2)

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

counter = 0
for i <- 1..10 do
  counter = counter + 1
end
IO.inspect counter # prints 0

Еще один способ думать об этом состоит в том, что внутренняя counter - это просто новая переменная, имя которой совпадает с именемвнешний.Приведенный выше пример эквивалентен:

counter = 0
for i <- 1..10 do
  new_counter = counter + 1
end

Чтобы обойти это, как вы уже правильно заметили, мы используем такие функции, как Enum.reduce/2-3, которые позволяют нам сохранять промежуточные результаты в аккумулятор .Таким образом, каждая из переменных, которые нужно запомнить, должна быть в аккумуляторе.Значение для следующей итерации затем возвращается из анонимной функции, которая позволяет Enum.reduce передать его обратно в следующую итерацию.

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

result = Enum.reduce list, {0, len}, fn {sum, len} ->
  new_sum = sum + ...
  new_len = ...
  {new_sum, new_len}
end
{sum, _} = result
sum

Если вы хотите сделать еще один шаг и лучше понять, как все эти части сочетаются друг с другом, я настоятельно рекомендую прочитать первые несколько глав Эликсир программирования Дэйва Томаса .Он включает в себя некоторые упражнения для создания таких утилит, как Enum.reduce с нуля, что очень помогает.

В качестве заключительного замечания, в вашем коде есть некоторые вещи, которые можно улучшить.Например, он с радостью примет недопустимые цифры, такие как «2».Как вдохновение, вот как я бы решил эту проблему:

string
|> String.graphemes
|> Enum.reverse
|> Enum.with_index
|> Enum.reduce(0, fn
  {"0", _}, acc -> acc
  {"1", i}, acc -> acc + :math.pow(2, i)
end)
|> trunc()

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

3 голосов
/ 26 марта 2019

Вы должны возвращать новый acc в каждой итерации.В настоящее время последнее выражение в функции «обратного вызова» (то есть fun в reduce(enumerable, acc, fun)) имеет значение IO.puts, которое возвращает :ok.Вы должны построить новый acc в каждой итерации и вернуть его, который будет использоваться в следующей итерации.

Обратите внимание, что в Elixir структуры данных являются неизменяемыми.Также помните, что вы не изменяете len, существующий в верхней области видимости.По сути, вы делаете throw_away = len - 1.

Проверьте этот ответ , если хотите узнать больше о области видимости переменной.

1 голос
/ 26 марта 2019

Использование Enum.reduce так, как вы это делаете, не лучший подход к вашей проблеме.

Это потому, что каждая итерация Enum.reduce должна «возвращать» аккумулятор для следующей итерации.

Когда вы помещаете acc = acc + temp * power и len = len-1, вы не назначаете acc и len для значений, которые вы даете им в следующей итерации, только для текущей и фактически, вы должны получать предупреждения о том, что эти переменные не используются.

В конце вашего блока, поскольку последнее, что вы делаете, это len = len - 1, и, поскольку len всегда будет одинаковым, то, что вы передаете на следующую итерацию, всегда 1, в случае ввода "11".

И когда вы добавляете IO.puts/1, в конце вы передаете :ok («возврат» IO.puts) на следующую итерацию, которая выдает ошибку, которую вы видите.

альтернативный подход с использованием Enum.reduce может состоять в том, чтобы сохранить кортеж {len, sum} и в конце блока всегда ставить новые значения для len и sum.

Итак, вы можете изменить его следующим образом:

def to_decimal(string) do
  list = String.graphemes(string)
  len = length(list)

  Enum.reduce(list, {0, len}, fn x, {sum, len} ->
    temp = String.to_integer(x)
    power = :math.pow(2, len - 1) |> round()
    sum = sum + temp * power
    len = len - 1

    {sum, len}
  end)
end

В конце будет возвращен кортеж, содержащий {n, l}, где n будет десятичным представлением, а l - конечная длина.

...