我要传递一个由整数组成的列表,作为Enum.reduce中的字符串,并在列表的每个值上运行一个函数。但是问题是在第二次迭代中它返回:ok,由于哪个函数给了错误,我在代码中使用了多个IO.puts只是为了跟踪错误。
下面是我的函数to_decimal,当我们将“ 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
答案 0 :(得分:3)
您应该在每次迭代中返回新的acc
。当前,“回调”函数中的最后一个表达式(即reduce(enumerable, acc, fun)
中的fun
)是IO.puts
,它返回:ok
。您应该在每次迭代中构建新的acc
并返回它,该值将在下一次迭代中使用。
请注意,在Elixir中,数据结构是不可变的。还要注意,您不会更改存在于上限范围内的len
。基本上您在做的是throw_away = len - 1
。
如果想进一步了解变量范围,请查看this answer。
答案 1 :(得分:3)
对于真实世界的代码,您应该使用预发布的解决方案:
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
如果您想更进一步,以更好地了解所有这些部分如何组合在一起,我强烈建议您阅读Programming Elixir by Dave Thomas的前几章。它包括一些练习,从头开始构建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
模块的尾递归函数,但我将保留它供您探索。
答案 2 :(得分:1)
按照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
的新值。 / p>
因此,您可以将其更改为:
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
是最终长度。