【问题标题】:Check if all the elements of a Julia array are equal检查 Julia 数组的所有元素是否相等
【发布时间】:2018-05-13 20:56:55
【问题描述】:

我能想到的测试数组arr 中的所有元素是否相等的最短方法是all(arr[1] .== arr)。虽然这肯定很短,但似乎有点不雅。有没有内置函数可以做到这一点?

我怀疑有一些类似于==(arr...) 的东西,但这不起作用,因为== 运算符只能接受两个参数。我不确定 Julia 是如何解析 arr[1] == arr[2] == arr[3] 之类的表达式的,但是有没有办法让它适应具有任意数量元素的数组?

【问题讨论】:

    标签: arrays julia equality


    【解决方案1】:

    all 是正确的解决方案,但您需要方法 all(p, itr) 用于谓词 p 和可迭代的 itr,因为它会采用短路行为(一旦找到 false 就中断) .所以:

    all(y->y==x[1], x)
    

    要查看差异,您可以运行以下小速度测试:

    for n = 100000:250000:1100000
        x = rand(1:2, n);
        @time all(x .== x[1]);
        @time all(y->y==x[1], x);
        println("------------------------")
    end
    

    忽略第一次迭代,因为它是计时编译时间。

      0.000177 seconds (22 allocations: 17.266 KiB)
      0.006155 seconds (976 allocations: 55.062 KiB)
    ------------------------
      0.000531 seconds (23 allocations: 47.719 KiB)
      0.000003 seconds (1 allocation: 16 bytes)
    ------------------------
      0.000872 seconds (23 allocations: 78.219 KiB)
      0.000001 seconds (1 allocation: 16 bytes)
    ------------------------
      0.001210 seconds (23 allocations: 108.781 KiB)
      0.000001 seconds (1 allocation: 16 bytes)
    ------------------------
      0.001538 seconds (23 allocations: 139.281 KiB)
      0.000002 seconds (1 allocation: 16 bytes)
    

    第一个解决方案显然是 O(n),而第二个解决方案最多为 O(1),最坏为 O(n)(取决于itr 的数据生成过程)。

    【讨论】:

    • 哇,好答案!虽然我很惊讶没有比手动比较第一个元素更紧凑的语法了。
    • @tparker allsame(x) = all(y->y==x[1],x)。该语言的优点之一是您自己编写这些快捷方式并将它们放在您自己的核心包中是多么容易。更一般地说,Julia 社区强烈要求保持Base 尽可能简洁。每种操作的漂亮、简单的函数名称都可以放在包中,如果有足够多的人喜欢它,包就会变得流行,依此类推……但Base 仍然保持精简和快速。
    • 很高兴这甚至适用于空数组:allsame([]) == true,因为它应该是,因为空数组中没有不同的元素。
    【解决方案2】:

    很好的问题@tparker 和很好的答案@ColinTBowers。在尝试同时考虑它们时,我突然想到尝试直接的老派 Julian 方式-of-the-for-loop。结果在相同元素的长向量的重要输入上更快,所以我添加了这个注释。此外,函数名称allequal 似乎足以提及。所以这里是变种:

    allequal_1(x) = all(y->y==x[1],x)
    
    # allequal_2(x) used to be erroneously defined as foldl(==,x)   
    
    @inline function allequal_3(x)
        length(x) < 2 && return true
        e1 = x[1]
        i = 2
        @inbounds for i=2:length(x)
            x[i] == e1 || return false
        end
        return true
    end
    

    还有基准:

    julia> using BenchmarkTools
    
    julia> v = fill(1,10_000_000);  # long vector of 1s
    
    julia> allequal_1(v)
    true
    
    julia> allequal_3(v)
    true
    
    julia> @btime allequal_1($v);
      9.573 ms (1 allocation: 16 bytes)
    
    julia> @btime allequal_3($v);
      6.853 ms (0 allocations: 0 bytes)
    

    更新:另一个重要的基准测试案例是存在短路机会时。所以(根据评论中的要求):

    julia> v[100] = 2
    2
    
    julia> allequal_1(v),allequal_2(v),allequal_3(v)
    (false, false, false)
    
    julia> @btime allequal_1($v);
      108.946 ns (1 allocation: 16 bytes)
    
    julia> @btime allequal_3($v);
      68.221 ns (0 allocations: 0 bytes)
    

    在所有条件相同的情况下,for 版本在 Base 中应该是 allequal

    【讨论】:

    • 谢谢,foldl 正是我正在寻找的功能。除了@inbounds,您认为第三个实现速度更快的原因是什么?
    • 我真的很喜欢 Julia Base 是如何通过组合和构建在通用函数之上的方式来构建的,它使许多实现变得高层次和抽象。以reduce.jl 为例。我希望allequal(尽管它实际上不应该在 Base 中)是这样的,建立在像 all 这样的通用结构之上,而不是制作一些超优化的自定义循环。这样,提高all 的性能将改进依赖它的每个函数。
    • 可能值得在同一台机器上为一个早期元素不同的长向量添加一个基准,看看缺少短路有多大。
    • 请注意,allequal_2 的最优性是题外话,因为它不正确!例如,allequal_2([0,0,0]) 的计算结果为 false
    • @banbh 感谢您的注意。我将编辑答案。可能被 true == 1false == 0 弄糊涂了,它们有可疑的类 C 起源。
    【解决方案3】:

    只是一点点改进:allsame(x) = all(y -&gt; y == first(x), x)allsame(x) = all(y -&gt; y == x[1], x) 更通用,即使在 x 不是 AbstractArray 时也可以使用,例如发电机。

    【讨论】:

    • 特别是,它适用于具有非标准索引的数组,例如从零开始的数组。顺便说一句,如果没记错的话,你可以写==(first(x)) 而不是y -&gt; y == first(x)。那个是新的。
    • 什么是发电机?
    • 这是一个 Julia 对象,可以迭代生成值,而无需为它们分配容器或将它们写入内存。例如,像c = [x^2 for x in 1:100] 这样的推导式分配了一个由前 100 个整数的平方组成的向量。相反,生成器g = (x^2 for x in 1:100) 按顺序生成相同的值,而不分配向量。您可以执行for s in g; $show s; end 之类的操作来打印它们。生成器被证明是一种非常强大的性能工具。
    猜你喜欢
    • 2015-02-12
    • 1970-01-01
    • 2019-07-04
    • 2012-12-16
    • 2019-05-25
    • 2013-01-27
    • 1970-01-01
    • 2017-11-01
    相关资源
    最近更新 更多