我遇到了Mark Dominus' blog post,它描述了使用函数式编程技术(特别是Monads)在Python中解决"SEND+MORE=MONEY" puzzle。
这里的谜题总结了死链接:
S E N D | Find each character's *unique* numerical value, such that
+ M O R E | the addition on the left is valid. There are no leading zeros.
-----------
= M O N E Y
我一直在寻找机会学习一些纯函数编程,特别是Elixir,这看起来非常合适。
我可以在Elixir中实现美国相似版本的Mark Dominus的Python代码:
defmodule Smm do
def make_set(ls), do: Enum.into(ls, HashSet.new)
def to_number([]), do: :error
def to_number(ls), do: Enum.join(ls) |> Integer.parse |> elem(0)
def remove(hs, ls), do: Set.difference(hs, Enum.into(ls, HashSet.new))
def let(x, func), do: func.(x)
def guard(predicate, func) when predicate, do: func.()
def guard(predicate, func), do: []
end
digits = Smm.make_set(0..9)
Enum.map( Smm.remove(digits, [0]), fn s ->
Enum.map( Smm.remove(digits, [s]), fn e ->
Enum.map( Smm.remove(digits, [s,e]), fn n ->
Enum.map( Smm.remove(digits, [s,e,n]), fn d ->
Smm.let(Smm.to_number([s,e,n,d]), fn w_send ->
Enum.map( Smm.remove(digits, [0,s,e,n,d]), fn m ->
Enum.map( Smm.remove(digits, [s,e,n,d,m]), fn o ->
Enum.map( Smm.remove(digits, [s,e,n,d,m,o]), fn r ->
Smm.let(Smm.to_number([m,o,r,e]), fn w_more ->
Enum.map( Smm.remove(digits, [s,e,n,d,m,o,r]), fn y ->
Smm.let(Smm.to_number([m,o,n,e,y]), fn w_money ->
Smm.guard(w_send + w_more == w_money, fn ->
[w_send, w_more, w_money] |> Enum.map( &(IO.puts(&1)) )
end)end)end)end)end)end)end)end)end)end)end)end) # (╯°□°)╯︵ ┻━┻
但有些东西告诉我必须绕过疯狂嵌套的匿名函数和随后的表格翻转;这就是为什么存在纯函数式语言的原因吧?
看着Mark Dominus'previous blog post in which he solves the puzzle with Haskell,我看到他正在使用含糖版本的Haskell的“绑定”运算符>>=
来消除表格翻转的冲动......但我没有{{3所以我对这篇博文中提供的代码没有很强的把握。
我很确定我在Elixir实现中缺少的是使用管道运算符|>
,这实际上对我来说是一个很大的吸引力(我非常熟悉Unix管道) 。我尝试过使用管道和Enum.{map,reduce}
的许多种类,但我总是回到第一个方向。
有人可以提供任何建议吗?理想情况下,我正在为Elixir中的这个难题寻找更具惯用性的函数式编程解决方案。
答案 0 :(得分:3)
您可以在此处查看:What is the "|>" symbol's purpose in Elixir?,了解|>
运算符的概述。但基本的想法是a |> f(b, c)
与f(a, b, c)
相同。当您执行a |> f(b) |> g(c)
之类的操作时,这非常有用,上面的规则与g(f(a, b), c)
相同,但读取得更好。
据说,|>
运算符(称为管道)不是monadic绑定(>>=
),并且不会让你“变平”#34;像>>=
这样的深层嵌套循环。对于在Elixir中看起来更好的任务的替代方法,您可以:
停止使用这种循环方法,例如使用递归函数预先生成数字到字母的赋值,如下所示:
defmodule Smm do
# some more things
def assignments(0, _), do: [[]]
def assignments(n, digits \\ Enum.into(0..9, HashSet.new)) do
digits
|> Stream.flat_map(fn (d) ->
for rest <- assignments(n - 1, Set.delete(digits, d)) do
[d | rest]
end
end)
end
end
for [s, e, n, d, m, o, r, y] <- Smm.assignments(8) do
w_send = Smm.to_number([s, e, n, d])
w_more = Smm.to_number([m, o, r, e])
w_money = Smm.to_number([m, o, n, e, y])
if s > 0 && m > 0 && (w_send + w_more == w_money) do
IO.inspect([w_send, w_more, w_money])
end
end
答案 1 :(得分:3)
这种语法分心在Haskell中被消除了两件事:运算符关联性和lambdas的一个很好的语言规则。
让associativity rules消除语法中不必要的括号。例如,在Elixir中,添加关联到左侧,因此当您编写a + 2 + x
时,它将被解释为(a + 2) + x
。关联性规则可以让你摆脱括号。如果您的意思是a + (2 + x)
,则必须明确地写出来。
你可以获得操作员关联来帮助Elixir,有些人已经有了。 MonadEx库定义了一个绑定操作符~>>
,可以让你编写程序的内容大致为
Smm.remove(digits, [0])
~>> fn s -> Smm.remove(digits, [s])
~>> fn e -> Smm.remove(digits, [s,e])
~>> fn n -> Smm.remove(digits, [s,e,n])
~>> fn d -> return(Smm.to_number([s,e,n,d]))
~>> fn w_send -> Smm.remove(digits, [0,s,e,n,d])
~>> fn m -> Smm.remove(digits, [s,e,n,d,m])
~>> fn o -> Smm.remove(digits, [s,e,n,d,m,o])
~>> fn r -> return(Smm.to_number([m,o,r,e]))
~>> fn w_more -> Smm.remove(digits, [s,e,n,d,m,o,r])
~>> fn y -> return(Smm.to_number([m,o,n,e,y]))
~>> fn w_money -> Smm.guard(w_send + w_more == w_money)
~>> fn -> return([w_send, w_more, w_money])
end end end end end end end end end end end end
运算符关联性并没有消除所有以相同位置结尾的lambda表达式。后面的表达式需要在早期的lambdas中,以便他们可以看到前面介绍的变量。 Haskell通过简单的语法规则"lambda abstractions ... extend as far to the right as possible"摆脱了这种分心。因为lambdas一直延伸到右边,所以用相同样式编写的Haskell代码没有一大堆末端括号。
solutions = remove [0] digits >>= \s ->
remove [s] digits >>= \e ->
remove [s,e] digits >>= \n ->
remove [s,e,n] digits >>= \d ->
let send = to_number [s,e,n,d]
in remove [0,s,e,n,d] digits >>= \m ->
remove [s,e,n,d,m] digits >>= \o ->
remove [s,e,n,d,m,o] digits >>= \r ->
let more = to_number [m,o,r,e]
in remove [s,e,n,d,m,o,r] digits >>= \y ->
let money = to_number [m,o,n,e,y] in
guard (send + more == money) >>= \_ ->
return (send, more, money)
我无法想象Elixir的相应技巧。每个fn
最后都必须end
,因此会有与绑定一样多的end
个。我想你只需要继续翻转表格(╯°□°)╯︵ ┻━┻
。