Julia:计算排序数组中可能包含缺失元素的唯一元素数的最快方法

Julia:计算排序数组中可能包含缺失元素的唯一元素数的最快方法,julia,Julia,关键是数组已排序,可能包含缺少的元素。我怀疑长度(unique(arr))可能不是最快的 我想知道是否有一个预构建的函数可以处理这种情况?Julia的函数对于任意集合非常有效——时间与输入集合的大小近似成线性关系。但是,由于它构造了两个查找字典来帮助识别以前见过的元素,因此它分配的内存量会随着集合中唯一元素的数量而增加。如果您知道输入集合已排序,则可以利用这一点来减少分配并显著加快计数: function nunique(a) last = first(a) n = 1

关键是数组已排序,可能包含缺少的元素。我怀疑
长度(unique(arr))
可能不是最快的

我想知道是否有一个预构建的函数可以处理这种情况?

Julia的函数对于任意集合非常有效——时间与输入集合的大小近似成线性关系。但是,由于它构造了两个查找字典来帮助识别以前见过的元素,因此它分配的内存量会随着集合中唯一元素的数量而增加。如果您知道输入集合已排序,则可以利用这一点来减少分配并显著加快计数:

function nunique(a)
    last = first(a)
    n = 1
    for x in a
        if isless(last, x)
            n += 1
            last = x
        end
    end
    n
end

r = Array{Union{Missing,Int64}}(rand(1:10000, 100000))  # 100_000 elements, 10_000 unique
r[rand(1:length(r), 100)] .= missing                    # 100 missing elements
sort!(r)

@time length(unique(r))
# 0.002156 seconds (37 allocations: 503.781 KiB)
# 10001

@time nunique(r)
# 0.000464 seconds (1 allocation: 16 bytes)
# 10001
就我所知,没有为排序输入数组的特殊情况进行优化的内置函数

此函数仍将像输入集合的大小一样在时间上进行缩放,但它只进行一次(!)分配,因此它可以减少创建查找字典所涉及的所有开销

当然,只有当数组已经按照
isless
函数进行排序时,此函数才起作用。您可以在迭代中止时添加检查,并在必要时切换到
长度(唯一(itr))
版本:

function nunique2(a)
    last = first(a)
    n = 1
    for x in a
        if isless(last, x)
            n += 1
            last = x
        elseif !isequal(last, x)
            return length(unique(a))
        end
    end
    n
end

@time nunique2(r)
# 0.000256 seconds (1 allocation: 16 bytes)
# 10001

using Random
shuffle!(r)
@time nunique2(r)
# 0.002801 seconds (37 allocations: 503.781 KiB)
# 10001

与所有微基准一样,YMMV。

我有一个并行解决方案,但遗憾的是,它在6个线程上只快了30%

function nunique(v)
    @assert length(v) > 0
    cnt = 1
    lasta::eltype(v) = first(v)
    @inbounds for newa1 in v
        if !isequal(newa1, lasta)
            cnt += 1
            lasta = newa1
        end
    end
    cnt
end

""" Parallelized version """
function pnunique(v)
    nt = Threads.nthreads()
    lo::Vector{Int} = collect(0:div(length(v), nt):length(v)-1)
    hi::Vector{Int} = lo[2:end]
    hi[end] = length(v)
    lo = lo .+ 1

    nu = Vector{Int}(undef, nt)

    Threads.@threads for i in 1:nt
        @inbounds nu[i] = nunique(@view v[lo[i]:hi[i]])
    end

    res = sum(nu)
    for j in 1:nt-1
        @inbounds res -= v[hi[j]] == v[lo[j+1]]
    end

    res
end