Zipping Julia arrays together
ZippedArrays
is a Julia package to zip several (abstract) arrays
together for accessing their elements simultaneously. For instance, assuming
that A
, B
and C
are 3 Julia arrays, then:
using ZippedArrays
Z = ZippedArray(A,B,C)
builds a zipped array instance Z
such that the syntax Z[i]
yields the
3-tuple (A[i],B[i],C[i])
while the syntax Z[i] = (a,b,c)
is equivalent to
(A[i],B[i],C[i]) = (a,b,c)
.
Any number of arrays can be zipped together, they must however have the same
indices (as returned by the axes
method).
To build an uninitialized zipped array of size dims
whose elements are tuples
of items of types T1
, T2
, etc., call:
Z = ZippedArray{Tuple{T1,T2,...}}(undef, dims)
For example:
Z = ZippedArray{Tuple{Int,Float64}}(undef, 2, 3, 4)
builds a 3-dimensional array of size (2,3,4)
and whose elements are 2-tuples
of type Tuple{Int,Float64}
.
Compared to the zip
function which only provides means to iterate through its
arguments, a zipped array can be accessed in random order and for reading and
writing. This makes zipped arrays useful for multi-key sorting. For instance:
sort!(ZippedArray(A,B);
lt = (x,y) -> ifelse(x[1] == y[1], x[2] < y[2], x[1] < y[1]))
will sort in-place vectors A
and B
such that the values in A
are in
increasing order and, in case of equality, the values in B
are in increasing
order.
A zipped array is a simple immutable structure wrapped around the arguments of
ZippedArray
so zipped arrays are almost costless to build. Below is an
example of how to build an array C
whose elements are pairs of values from
A
and B
and a zipped array Z
also built from A
and B
:
using ZippedArrays
n = 10_000
A = rand(Float64, n)
B = rand(Int64, n)
C = [(A[i],B[i]) for i in 1:n]
Z = ZippedArray(A,B)
C == Z # yields true
The comparison C == Z
shows that the two arrays are virtually the same
(although not the same object, that is C !== Z
). Building Z
however
requires no copy of array elements and hardly requires additional memory, the
sizes of Z
and C
are indeed quite different:
julia> sizeof(Z)
16
julia> sizeof(C)
160000
These numbers may depend on the architecture (here a 64-bit processor).
Thanks to the in-lining of functions and optimizations, a zipped array may also
be faster. For instance, with the arrays C
and Z
defined above:
using BenchmarkTools
function sum_first(A::AbstractArray{<:Tuple})
s = 0.0
@inbounds @simd for i in eachindex(A)
s += first(A[i])
end
return s
end
@btime sum_first($C) # 1.615 μs (0 allocations: 0 bytes)
@btime sum_first($Z) # 643.983 ns (0 allocations: 0 bytes)