【问题标题】:List comprehension in Haskell, Python and RubyHaskell、Python 和 Ruby 中的列表理解
【发布时间】:2012-03-16 12:48:00
【问题描述】:

我已经开始将 project Euler site 视为学习 Haskell 并改进我的 Python 和 Ruby 的一种方式。我认为 Haskell 和 Python 版本还可以,但我确信 Ruby 必须有更清洁的方法。

这不是关于如何让一种语言看起来像另一种语言。

这是Problem 1

问:将所有小于一千且是 3 或 5 倍数的自然数相加。

哈斯克尔:

sum [ x | x <- [1..999], mod x 3 == 0 || mod x 5 == 0 ]

Python:

sum ( [ x for x in range(1,1000) if x % 3 == 0 or x % 5 == 0 ] )

鲁比:

(1..999) . map {|x| x if x % 3 == 0 || x % 5 == 0 } . compact . inject(:+)

他们都给出了相同的答案。


好的,所以 Python 可以变成:

sum ( x for x in range(1,1000) if x % 3 == 0 or x % 5 == 0 )

它现在是一个生成器(这是一件好事,因为我们没有存储列表)

但更有趣的是:

sum( set(range(0,1000,3)) | set(range(0,1000,5)) )

出于某种原因,我再次查看此内容并尝试了一种应该是恒定时间的求和方法。在 Python 3 中:

def step_sum(mn,mx,step):
    amax = mx - (mx - mn) % step
    return (mn + amax) * ((1 + ((amax - mn) / step)) / 2)

step_sum(3,999,3) + step_sum(5,999,5) - step_sum(15,999,15)

Ruby 可以变成:

(1..999) . select {|x| x % 3 == 0 || x % 5 == 0} . inject(:+)

(1..999) . select {|x| x % 3 == 0 or x % 5 == 0} . reduce(:+)

我假设与map 不同,select 不会产生“nul”,因此无需调用compact。不错。

Haskell 也可以是:

let ƒ n = sum [0,n..999] in ƒ 3 + ƒ 5 - ƒ 15

或者说得更清楚:

let ƒ n = sum [ 0 , n .. 999 ] in ƒ 3 + ƒ 5 - ƒ (lcm 3 5)

作为一个让我们自己提供两个数字的函数:

ƒ :: (Integral a) => a -> a -> a
ƒ x y = let ƒ n = sum [0,n..999] in ƒ x + ƒ y - ƒ (lcm x y)

【问题讨论】:

  • Ruby 总是让我想起 Perl... 强大而紧凑的语法,但有时几周后您很难阅读自己的代码。
  • 嗨,我不了解 ruby​​,这并不能回答您的问题,但是...恭喜您选择了 Project Euler,这太棒了...对于那个特定的问题,有一个恒定的时间解决方案问题,如果你想优化一点;)如果你不知道 Summation,检查一下,它会变得非常方便。
  • 在 Python 中,您可以删除方括号。
  • @Jwosty 您的意思是代码还是您对这个问题持哲学态度?如果是代码......那么它应该提供问题'Q'的答案。
  • @eumiro:删除方括号会将列表理解转换为生成器,如果您不打算存储列表,这是一个非常好的主意(性能方面)。

标签: python ruby haskell list-comprehension


【解决方案1】:

我喜欢 Haskell

let s n = sum [0,n..999] in s 3 + s 5 - s 15

sum $ filter ((>1).(gcd 15)) [0..999]

为了好玩,Rube-Goldberg 版本:

import Data.Bits

sum $ zipWith (*) [1..999] $ zipWith (.|.) (cycle [0,0,1]) (cycle [0,0,0,0,1])

好的,解释时间。

第一个版本定义了一个小函数 s,它将 n 的所有倍数相加到 999。如果我们将所有 3 的倍数和所有 5 的倍数相加,我们将所有 15 的倍数包括两次(在每个列表中一次),因此我们需要减去一次。

第二个版本使用了 3 和 5 是素数的事实。如果一个数包含因数 3 和 5 中的一个或两者,则该数和 15 的 gcd 将为 3、5 或 15,因此在任何情况下 gcd 都将大于 1。对于没有与 15 的公因数的其他数字,gcd 变为 1。这是一步测试两个条件的好方法。但要小心,它不适用于任意数字,例如当我们有 4 和 9 时,测试 gdc x 36 &gt; 1 将不起作用,就像 gcd 6 36 == 6,但 mod 6 4 == 0mod 6 9 == 0 都不起作用。

第三个版本很有趣。 cycle 一遍又一遍地重复一个列表。 cycle [0,0,1] 将“可除性模式”编码为 3,cycle [0,0,0,0,1] 编码为 5。然后我们使用 zipWith 将这两个列表“或”在一起,得到 [0,0,1,0,1,1,0,0,1,1,0,1...]。现在我们再次使用zipWith 将其与实际数字相乘,得到[0,0,3,0,5,6,0,0,9,10,0,12...]。然后我们把它加起来。

知道做同一件事的不同方法对于其他语言来说可能是浪费,但对于 Haskell 来说这是必不可少的。你需要发现模式,掌握技巧和习语,并经常玩耍,以获得有效使用这种语言的心理灵活性。像项目欧拉问题这样的挑战是一个很好的机会。

【讨论】:

  • 你能解释一下你的答案吗(1天的haskell对我来说意味着一切都是新的)......第一个似乎是一个递归函数。 15是最小公倍数? [0,n..999] 将是模式...即 0,3,6,9 ... ] 并且 n 的值在 s 3 ... s 5 ...
  • @user969617:包含-排除原则。
  • @Vitus 我已经编辑了我的问题以包含这个 Haskell 代码。它只是在盯着它看了一分钟后才点击:)
  • @user969617:忽略我之前(已删除)的评论,我没有注意到前面的 let。 :)
  • @Landei 感谢您的解释。我已经更新了我的原始问题以包含您包含的第一个示例 - 因为我制定了如何使用它。学习 Haskell 很有趣 - 一方面它违背了我的预期,另一方面它正是我认为应该是的(即......函数/来自非函数式语言时)......它看起来也很漂亮。
【解决方案2】:

在 Ruby 上试试这个:

(1..999).select {|x| x % 3 == 0 or x % 5 == 0}.reduce(:+)

或者有点不同的方法:

(1..999).reduce(0) {|m, x| (x % 3 == 0 or x % 5 == 0) ? m+x : m }

【讨论】:

  • 你打败了我。不过,我更喜欢“减少”而不是“注入”。
  • 是的,reduce 更短,更直观的命名。
  • select 非常清楚,只要您不需要处理所选值(然后您需要select + map),这就是列表理解的亮点。在 Ruby 中,您可以编写 map_select,但它没有那么漂亮(更不用说嵌套列表理解)。
  • 我不太喜欢像这样使用orand。见devblog.avdi.org/2010/08/02/using-and-and-or-in-ruby
  • 另外,您的第二个示例添加了 3 和 5 的倍数。
【解决方案3】:

我知道,这不是列表理解,但要解决我会使用:

3*((999/3)**2+999/3)/2+5*((999/5)**2+999/5)/2-15*((999/15)**2+999/15)/2

比任何人可能想出的任何列表理解都要快,并且可以使用任何语言;)

仅发布以显示使用http://en.wikipedia.org/wiki/Summation 来查看同一问题的另一种方式。

【讨论】:

  • 太慢了!...print 233168呢?
  • 好吧...我想区别在于只有 999 是硬编码的,对吧?和上面的列表理解一样吗?...我知道将这个幻数重构为函数参数会真的很难...
  • haskell 返回:233366.40000000002 python 和 ruby​​:233168
  • 嗯...没有做任何事情,我想这与作为浮点数返回的除法有关。在 ruby​​ 和 python 中,两个整数的除法被自动假定为整数除法。
  • pcalcao 是正确的。 haskell 中的整数除法是div :: Integral a =&gt; a -&gt; a -&gt; a。与(/) :: Fractional a =&gt; a -&gt; a -&gt; a 比较。 (**) :: Fractional a =&gt; a -&gt; a -&gt; a(^) :: (Num a, Integral b) =&gt; a -&gt; b -&gt; a 相同。
【解决方案4】:

我认为以下是一个更好的 Ruby:

(1..999).select{|x| x % 3 == 0 || x % 5 == 0}.reduce(:+)

【讨论】:

  • detail: reduce(0, :+) 不假设有多少个值会被求和。
【解决方案5】:

试试这样的:

(1...1000).inject(0) do |sum, i|
  if (i % 3 == 0) or (i % 5 == 0)
    sum + i
  else
    sum
  end

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-08
    • 1970-01-01
    相关资源
    最近更新 更多