Skip to content

Commit

Permalink
Merge pull request #25 from JuliaArrays/teh/dict
Browse files Browse the repository at this point in the history
This is primarily intended for ImageSegmentation, for which the
index label is not guaranteed to be contiguous.

* Add Manifest to .gitignore

* Use testsets
  • Loading branch information
timholy authored Sep 1, 2021
2 parents 3a821ba + 8ee63e7 commit e5541f5
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 70 deletions.
1 change: 0 additions & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ jobs:
fail-fast: false
matrix:
version:
- '0.7'
- '1.0'
- '1'
- 'nightly'
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.jl.cov
*.jl.*.cov
*.jl.mem
Manifest.toml
7 changes: 4 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
name = "IndirectArrays"
uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959"
version = "0.5.1"
version = "1.0.0"

[deps]

[compat]
FixedPointNumbers = "0.5, 0.6, 0.7"
julia = "0.7, 1"
julia = "1"

[extras]
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
MappedArrays = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test", "MappedArrays", "Colors", "FixedPointNumbers"]
test = ["Test", "MappedArrays", "Colors", "FixedPointNumbers", "OrderedCollections"]
54 changes: 37 additions & 17 deletions src/IndirectArrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,42 @@ export IndirectArray
IndirectArray(index, values)
creates an array `A` where the values are looked up in the value table,
`values`, using the `index`. Concretely, `A[i,j] =
values[index[i,j]]`.
`values`, using the `index`. Concretely, `A[i...] =
values[index[i...]]`.
`values` can be an `AbstractVector` (useful when the values in `index` are
contiguous) or an `AbstractDict` (useful when they are not).
When there are not very many such distinct values, "frozen" LittleDicts from
[OrderedCollections](https://github.com/JuliaCollections/OrderedCollections.jl)
are recommended for performance.
"""
struct IndirectArray{T,N,A<:AbstractArray{<:Integer,N},V<:AbstractVector{T}} <: AbstractArray{T,N}
struct IndirectArray{T,N,I,A<:AbstractArray{I,N},V<:Union{AbstractVector{T},AbstractDict{I,T}}} <: AbstractArray{T,N}
index::A
values::V

@inline function IndirectArray{T,N,A,V}(index, values) where {T,N,A,V}
@inline function IndirectArray{T,N,I,A,V}(index, values) where {T,N,I,A,V}
# The typical logic for testing bounds and then using
# @inbounds will not check whether index is inbounds for
# values. So we had better check this on construction.
@boundscheck checkbounds(values, index)
new{T,N,A,V}(index, values)
if isa(values, AbstractVector)
I <: Integer || error("with a vector `values`, the index must have integer eltype")
@boundscheck checkbounds(values, index)
else
@boundscheck begin
uindex = unique(index)
all(idx -> haskey(values, idx), index) || Base.throw_boundserror(index, values)
end
end
new{T,N,I,A,V}(index, values)
end
end
const IndirectArrayVec{T,N,I,A,V<:AbstractVector} = IndirectArray{T,N,I,A,V}

Base.@propagate_inbounds IndirectArray(index::AbstractArray{<:Integer,N}, values::AbstractVector{T}) where {T,N} =
IndirectArray{T,N,typeof(index),typeof(values)}(index, values)
IndirectArray{T,N,eltype(index),typeof(index),typeof(values)}(index, values)
Base.@propagate_inbounds IndirectArray(index::AbstractArray{I,N}, values::AbstractDict{I,T}) where {I,T,N} =
IndirectArray{T,N,I,typeof(index),typeof(values)}(index, values)

function IndirectArray{T}(A::AbstractArray) where {T}
values = unique(A)
index = convert(Array{T}, indexin(A, values))
Expand All @@ -32,30 +51,31 @@ IndirectArray(A::AbstractArray) = IndirectArray{UInt8}(A)

Base.size(A::IndirectArray) = size(A.index)
Base.axes(A::IndirectArray) = axes(A.index)
Base.IndexStyle(::Type{IndirectArray{T,N,A,V}}) where {T,N,A,V} = IndexStyle(A)
Base.IndexStyle(::Type{IndirectArray{T,N,I,A,V}}) where {T,N,I,A,V} = IndexStyle(A)

Base.copy(A::IndirectArray) = IndirectArray(copy(A.index), copy(A.values))
function Base.copy(A::IndirectArray)
@inbounds ret = IndirectArray(copy(A.index), copy(A.values))
return ret
end

@inline function Base.getindex(A::IndirectArray, i::Int)
@boundscheck checkbounds(A.index, i)
@inbounds idx = A.index[i]
@boundscheck checkbounds(A.values, idx)
@inbounds ret = A.values[idx]
ret
end

@inline function Base.getindex(A::IndirectArray{T,N}, I::Vararg{Int,N}) where {T,N}
@boundscheck checkbounds(A.index, I...)
@inbounds idx = A.index[I...]
@boundscheck checkbounds(A.values, idx)
@inbounds ret = A.values[idx]
ret
end

@inline function Base.setindex!(A::IndirectArray, x, i::Int)
@inline function Base.setindex!(A::IndirectArrayVec, x, i::Int)
@boundscheck checkbounds(A.index, i)
idx = findfirst(isequal(x), A.values)
if idx == nothing
if idx === nothing
push!(A.values, x)
A.index[i] = length(A.values)
else
Expand All @@ -64,9 +84,9 @@ end
return A
end

@inline function Base.push!(A::IndirectArray{T,1} where T, x)
@inline function Base.push!(A::IndirectArrayVec{T,1} where T, x)
idx = findfirst(isequal(x), A.values)
if idx == nothing
if idx === nothing
push!(A.values, x)
push!(A.index, length(A.values))
else
Expand All @@ -75,7 +95,7 @@ end
return A
end

function Base.append!(A::IndirectArray{T,1}, B::IndirectArray{T,1}) where T
function Base.append!(A::IndirectArrayVec{T,1}, B::IndirectArray{T,1}) where T
if A.values == B.values
append!(A.index, B.index)
else # pretty inefficient but let's get something going
Expand All @@ -86,7 +106,7 @@ function Base.append!(A::IndirectArray{T,1}, B::IndirectArray{T,1}) where T
return A
end

function Base.append!(A::IndirectArray{<:Any,1}, B::AbstractVector)
function Base.append!(A::IndirectArrayVec{<:Any,1}, B::AbstractVector)
for b in B
push!(A, b)
end
Expand Down
113 changes: 64 additions & 49 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,53 +1,68 @@
using IndirectArrays, MappedArrays
using IndirectArrays, MappedArrays, OrderedCollections
using Test, FixedPointNumbers, Colors

colors = [RGB(1,0,0) RGB(0,1,0);
RGB(0,0,1) RGB(1,0,0)]
index0 = [1 3;
2 1]
for indexT in (Int8, Int16, UInt8, UInt16)
A = IndirectArray{indexT}(colors)
@test eltype(A) == RGB{N0f8}
@test size(A) == (2,2)
@test ndims(A) == 2
@test A[1,1] === A[1] === RGB(1,0,0)
@test A[2,1] === A[2] === RGB(0,0,1)
@test A[1,2] === A[3] === RGB(0,1,0)
@test A[2,2] === A[4] === RGB(1,0,0)
@test isa(eachindex(A), AbstractUnitRange)
@test A.index == index0
end
x = IndirectArray(colors[:])
@test x == IndirectArray{UInt8}(colors[:])
xc = copy(x)
@test x == xc
xc[2], xc[3] = RGB(0,1,0), RGB(0,1,1)
@test xc == IndirectArray([RGB(1,0,0), RGB(0,1,0), RGB(0,1,1), RGB(1,0,0)])
@test append!(x, x) == IndirectArray([colors[:]; colors[:]])
@test append!(x, IndirectArray([RGB(1,0,0), RGB(1,1,0)])) ==
IndirectArray([colors[:]; colors[:]; [RGB(1,0,0), RGB(1,1,0)]])
# Append with non-IndirectArray
@test append!(IndirectArray(colors[:]), IndirectArray(colors[:])) ==
append!(IndirectArray(colors[:]), colors[:])
@testset "values::AbstractVector" begin
colors = [RGB(1,0,0) RGB(0,1,0);
RGB(0,0,1) RGB(1,0,0)]
index0 = [1 3;
2 1]
for indexT in (Int8, Int16, UInt8, UInt16)
A = IndirectArray{indexT}(colors)
@test eltype(A) == RGB{N0f8}
@test size(A) == (2,2)
@test ndims(A) == 2
@test A[1,1] === A[1] === RGB(1,0,0)
@test A[2,1] === A[2] === RGB(0,0,1)
@test A[1,2] === A[3] === RGB(0,1,0)
@test A[2,2] === A[4] === RGB(1,0,0)
@test isa(eachindex(A), AbstractUnitRange)
@test A.index == index0
end
x = IndirectArray(colors[:])
@test x == IndirectArray{UInt8}(colors[:])
xc = copy(x)
@test x == xc
xc[2], xc[3] = RGB(0,1,0), RGB(0,1,1)
@test xc == IndirectArray([RGB(1,0,0), RGB(0,1,0), RGB(0,1,1), RGB(1,0,0)])
@test append!(x, x) == IndirectArray([colors[:]; colors[:]])
@test append!(x, IndirectArray([RGB(1,0,0), RGB(1,1,0)])) ==
IndirectArray([colors[:]; colors[:]; [RGB(1,0,0), RGB(1,1,0)]])
# Append with non-IndirectArray
@test append!(IndirectArray(colors[:]), IndirectArray(colors[:])) ==
append!(IndirectArray(colors[:]), colors[:])

# Bounds checking upon construction
index_ob = copy(index0)
index_ob[1] = 5 # out-of-bounds
unsafe_ia(idx, vals) = (@inbounds ret = IndirectArray(idx, vals); ret)
safe_ia(idx, vals) = (ret = IndirectArray(idx, vals); ret)
@test_throws BoundsError safe_ia(index_ob, colors[1:3])
# This requires inlining, which means it fails on Travis since we turn
# off inlining for better coverage stats
# B = unsafe_ia(index_ob, colors)
# @test_throws BoundsError B[1]
# @test B[2] == RGB(0,0,1)

# Bounds checking upon construction
index_ob = copy(index0)
index_ob[1] = 5 # out-of-bounds
unsafe_ia(idx, vals) = (@inbounds ret = IndirectArray(idx, vals); ret)
safe_ia(idx, vals) = (ret = IndirectArray(idx, vals); ret)
@test_throws BoundsError safe_ia(index_ob, colors[1:3])
# This requires inlining, which means it fails on Travis since we turn
# off inlining for better coverage stats
# B = unsafe_ia(index_ob, colors)
# @test_throws BoundsError B[1]
# @test B[2] == RGB(0,0,1)
# Non-Arrays
a = [0.1 0.4;
0.33 1.0]
f(x) = round(Int, 99*x) + 1 # maps 0-1 to 1-100
m = mappedarray(f, a)
cmap = colormap("RdBu", 100)
img = IndirectArray(m, cmap)
@test img == [cmap[11] cmap[41];
cmap[34] cmap[100]]
end

# Non-Arrays
a = [0.1 0.4;
0.33 1.0]
f(x) = round(Int, 99*x) + 1 # maps 0-1 to 1-100
m = mappedarray(f, a)
cmap = colormap("RdBu", 100)
img = IndirectArray(m, cmap)
@test img == [cmap[11] cmap[41];
cmap[34] cmap[100]]
@testset "values::AbstractDict" begin
# With Dicts
a = ['a' 'b';
'q' 'j']
v = freeze(Dict('a' => "apple", 'b' => "book", 'q' => "quit", 'j' => "jolly"))
A = IndirectArray(a, v)
@test A[1,1] == "apple"
@test A[2,1] == "quit"
@test A[1,2] == "book"
@test A[2,2] == "jolly"
@test_throws BoundsError IndirectArray(a, Dict('a' => "apple", 'b' => "book"))
end

2 comments on commit e5541f5

@timholy
Copy link
Member Author

@timholy timholy commented on e5541f5 Sep 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/43949

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.0.0 -m "<description of version>" e5541f5190e4705da775404c1ba8ef635d20b63c
git push origin v1.0.0

Please sign in to comment.