Julia函数返回数组的非唯一元素

时间:2019-02-12 14:51:43

标签: julia

Julia base具有unique函数,该函数返回仅包含数组的唯一元素(或任何可迭代)的向量。我正在寻找一个nonunique函数来返回一个数组,该数组包含在其输入中至少出现两次的所有元素。据我所知,茱莉亚(Julia)没有这种功能,我感到有些惊讶。

我的第一次尝试如下:

function nonunique(x::AbstractArray)
    uniqueindexes = indexin(unique(x),x)
    nonuniqueindexes = setdiff(1:length(x),uniqueindexes)
    unique(x[nonuniqueindexes])
end

但是受Bogumił Kamiński'sindices of unique elements of vector in Julia的回答的启发,我写了第二个版本:

function nonunique(x::AbstractArray{T}) where T
    uniqueset = Set{T}()
    duplicatedset = Set{T}()
    duplicatedvector = Vector{T}()
    for i in x
        if(i in uniqueset)
            if !(i in duplicatedset)
                push!(duplicatedset, i)
                push!(duplicatedvector, i)
            end
        else
            push!(uniqueset, i)
        end
    end
    duplicatedvector
end

在我的测试中,该版本的速度提高了约4倍。它具有不错的属性,即按每个等效元素集的第二个(第一个重复)最初出现的顺序对返回进行排序。我相信,在检查in的成员资格时,SetArray更快,因为duplicatedset具有两个变量duplicatedvectornonunique

我真的有必要“推出自己的” package-lock.json函数吗,第二个版本可以改进吗?

3 个答案:

答案 0 :(得分:5)

通过对列表进行排序,然后搜索重复项,可以获得更高的性能:

function nonunique2(x::AbstractArray{T}) where T
    xs = sort(x)
    duplicatedvector = T[]
    for i=2:length(xs)
        if (isequal(xs[i],xs[i-1]) && (length(duplicatedvector)==0 || !isequal(duplicatedvector[end], xs[i])))
            push!(duplicatedvector,xs[i])
        end
    end
    duplicatedvector
end

以下是示例结果:

julia> x = rand(1:1000,1000);

julia> using BenchmarkTools

julia> nn = @btime nonunique($x);
  42.240 μs (39 allocations: 71.23 KiB)

julia> nn2s = @btime nonunique2($x);
  26.453 μs (10 allocations: 16.33 KiB)

julia> sort(nn) == sort(nn2s)
true

如果可以进行就地排序会更好:

function nonunique2!(x::AbstractArray{T}) where T
    sort!(x)
    duplicatedvector = T[]
    for i=2:length(x)
        if (isequal(x[i],x[i-1]) && (length(duplicatedvector)==0 || !isequal(duplicatedvector[end], x[i])))
            push!(duplicatedvector,x[i])
        end
    end
    duplicatedvector
end

以下是结果(相同的数据)

julia> nn2 = @btime nonunique2!($x)
  9.813 μs (9 allocations: 8.39 KiB)

julia> sort(nn) == sort(nns)
true

答案 1 :(得分:4)

要添加到上面的答案中,因为它的局限性是类型T必须是可排序的,并且它不是保留顺序的,所以我有两种可能的解决方案。

这是另一个使用StatsBase.jl的非订单保留解决方案。它可以比排序解决方案更快或更慢,具体取决于重复项的密度(它还可以完成更多工作,但在某些应用中,此信息可能有用):

nonunique3(x) = [k for (k, v) in countmap(x) if v > 1]

如果您想加快订单保留方法,可以执行以下操作:

function nonunique4(x::AbstractArray{T}) where T
    status = Dict{T, Bool}()
    duplicatedvector = Vector{T}()
    for i in x
        if haskey(status, i)
            if status[i]
                push!(duplicatedvector, i)
                status[i] = false
            end
        else
            status[i] = true
        end
    end
    duplicatedvector
end

通常,对它们进行基准测试比较棘手,因为性能取决于:

  • x中重复项和重复项的密度
  • T类型的大小(例如,如果它是非常大的不可变类型,则与标准情况相比可能会发生变化)

答案 2 :(得分:2)

并不是一个真正的答案(上面有出色的答案),但有一条评论可以将原始实现稍微清理一下:

function nonunique1(x::AbstractArray{T}) where T
    uniqueset = Set{T}()
    duplicatedset = Set{T}()
    for i in x
        if(i in uniqueset)
            push!(duplicatedset, i)
        else
            push!(uniqueset, i)
        end
    end
    collect(duplicatedset)
end

即您无需在推入集合之前检查是否存在,也无需填充向量和单独设置。它仍然不如排序实现快。