【问题标题】:Is there a way to avoid creating an array in this Julia expression?有没有办法避免在这个 Julia 表达式中创建数组?
【发布时间】:2013-07-05 04:08:03
【问题描述】:

有没有办法避免在这个 Julia 表达式中创建数组:

max((filter(n -> string(n) == reverse(string(n)), [x*y for x = 1:N, y = 1:N])))

并使其行为类似于此 Python 生成器表达式:

max(x*y for x in range(N+1) for y in range(x, N+1) if str(x*y) == str(x*y)[::-1])

由于数组分配和 N*N 次迭代与 Python 的 N*N/2 相比,Julia 版本比 Python 慢 2.3 倍。

编辑

在 Julia 中尝试了一些实现之后,我得到的最快的循环样式版本是:

function f(N)   # 320ms for N=1000  Julia 0.2.0 i686-w64-mingw32
    nMax = NaN
    for x = 1:N, y = x:N
        n = x*y 
        s = string(n)
        s == reverse(s) || continue
        nMax < n && (nMax = n)
    end 
    nMax
end 

但改进后的功能版本也紧随其后(如果您考虑 2 倍大的域,则仅慢 14% 或显着快):

function e(N)   # 366ms for N=1000  Julia 0.2.0 i686-w64-mingw32
    isPalindrome(n) = string(n) == reverse(string(n))
    max(filter(isPalindrome, [x*y for x = 1:N, y = 1:N]))
end 

与本页顶部的原始版本相比,通过定义isPalindrome 函数,性能提高了 2.6 倍。

【问题讨论】:

  • 干得好。你可以在这里写filter(isPalindrome, ...)
  • @StefanKarpinski 谢谢,现在看起来好多了。

标签: generator list-comprehension julia


【解决方案1】:

我们已经讨论过允许语法

max(f(x) for x in itr)

作为在一个协程中生成每个值f(x) 的简写,同时在另一个协程中计算最大值。这基本上是这样的简写:

max(@task for x in itr; produce(f(x)); end)

但是请注意,这种显式创建任务的语法已经有效,尽管它不如上面的漂亮。你的问题可以这样表达:

max(@task for x=1:N, y=x:N
    string(x*y) == reverse(string(x*y)) && produce(x*y)
end)

使用上面假设的生产者语法,它可以简化为这样的:

max(x*y if string(x*y) == reverse(string(x*y) for x=1:N, y=x:N)

虽然我是函数式风格的粉丝,但在这种情况下,我可能只会使用 for 循环:

m = 0
for x = 1:N, y = x:N
    n = x*y
    string(n) == reverse(string(n)) || continue
    m < n && (m = n)
end    

就个人而言,我不觉得这个版本更难阅读,而且在 Julia 中肯定会很快。一般来说,虽然函数式风格既方便又漂亮,但如果您的主要关注点是性能,那么显式 for 循环是您的朋友。不过,我们应该确保 John 的 max/filter/product 版本有效。 for 循环版本还使其他优化更容易添加,例如 Harlan 建议颠倒循环顺序并在您找到的第一个回文时退出。还有比实际创建和比较字符串更快的方法来检查一个数字是否是给定基数中的回文。

关于“在 Julia 中获得灵活的生成器和列表推导”的一般问题,该语言已经有了

  1. 一种基于 start/done/next 函数的通用高性能迭代协议。
  2. 比大多数语言更强大的多维数组解析。在这一点上,唯一缺少的功能是 if 保护,由于与多维理解的交互以及潜在地动态增长结果数组的需要而变得复杂。
  3. 协程(又称任务)允许生产者-消费者模式等模式。

Python 有 if 保护,但几乎不用担心理解性能 - 如果我们要将该功能添加到 Julia 的理解中,我们将以一种既快速又交互的方式来完成与多维数组很好,因此延迟。

更新: max 函数现在称为 maximummaximummaxsum+)和生成器语法和/或过滤器在 master 上工作,例如,您可以这样做:

julia> @time maximum(100x - x^2 for x = 1:100 if x % 3 == 0)
  0.059185 seconds (31.16 k allocations: 1.307 MB)
2499

一旦 0.5 出来,我会更彻底地更新这个答案。

【讨论】:

  • 如何使用 Julia 中的所有元编程优势将max(x*y if string(x*y) == reverse(string(x*y) for x=1:N, y=x:N) 直接转换为您在上面编写的循环,并避免协程调用开销和粘合逻辑。这可行吗?
  • 使用函数式风格的另一个原因是正确性保证。当你编写一段命令式代码时,你必须验证它是否正确,并且很容易沾沾自喜。我知道你现在很忙,但是你写的循环有两个问题:初始值应该是NaNmax = n 应该是nMax = max(n, nMax)。 OTAH,上面一行的声明性(功能性)版本没有这些问题。我不是要吹毛求疵,我只是想说明这一点:声明式max 是一个最大值,命令式最大值实现是不正确的,除非另有证明。
  • 我添加了最佳循环版本和修改功能版本的基准,仅慢了 16%。
  • 元编程不会为您购买新语法或自动翻译语法,它只允许您编写宏之类的东西。这种假设的语法不是宏——它是一种全新的语言特性,包括当前无效的 Julia 语法。
【解决方案2】:

这里有两个问题混合在一起:(1)你能过滤列表理解中间理解(答案目前是否定的)和(2)你能使用不分配数组的生成器(答案部分是肯定的)。生成器由 Iterators 包提供,但 Iterators 包目前似乎不能很好地与filter 配合使用。原则上,下面的代码应该可以工作:

max((x, y) -> x * y,
    filter((x, y) -> string(x * y) == reverse(string(x * y)),
           product(1:N, 1:N)))

【讨论】:

  • product 功能可用吗?我在哪里可以找到它?
【解决方案3】:

我不这么认为。 Julia 数组推导中目前没有过滤器。请参阅this issue 中的讨论。

在这种特殊情况下,如果您想获得更快的计算,我建议您只嵌套 for 循环。

(可能有更快的方法,您从 N 开始并倒数,一旦找到成功的东西就停止。弄清楚如何正确地做这件事留作练习等......)

【讨论】:

  • 我看过那个帖子。在 Julia 中获得灵活的生成器或列表推导式是否有希望?它们离 Matlab 范式太远了吗?至于改变算法,这是一个不同的练习,超出了我的问题范围。
【解决方案4】:

如前所述,这现在是可能的(使用 Julia 0.5.0)

isPalindrome(n::String) = n == reverse(n)
fun(N::Int) =  maximum(x*y for x in 1:N for y in x:N if isPalindrome(string(x*y)))

我相信还有其他更好的方法可供其他人评论。时间(热身后):

julia> @time fun(1000);
   0.082785 seconds (2.03 M allocations: 108.109 MB, 27.35% gc time)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 2015-03-05
    • 1970-01-01
    • 2010-09-24
    • 2019-09-07
    相关资源
    最近更新 更多