diff --git a/Project.toml b/Project.toml index 8e8bff2..16568ae 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "PairwiseListMatrices" uuid = "f9da4da7-9382-5435-b973-175f5d8dfb32" -version = "0.9.0" +version = "0.10.0" [deps] DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" diff --git a/src/PairwiseListMatrices.jl b/src/PairwiseListMatrices.jl index cb5a7e2..8abea1d 100644 --- a/src/PairwiseListMatrices.jl +++ b/src/PairwiseListMatrices.jl @@ -33,8 +33,12 @@ export PairwiseListMatrix, writedlm, apply2upper, apply2list, - apply2diag + apply2diag, + @iteratelist, + @iteratediag, + @iterateupper +include("macros.jl") include("pairwiselistmatrix.jl") include("apply.jl") include("plotrecipes.jl") diff --git a/src/macros.jl b/src/macros.jl new file mode 100644 index 0000000..cb3fd17 --- /dev/null +++ b/src/macros.jl @@ -0,0 +1,163 @@ +""" +The macro `@iteratelist` writes a `for` loop over the `list` but avoiding `getfield` calls +inside the loop. The first argument of the macro is the `PairwiseListMatrix` that is going +to be iterated and the second is the body of the loop. +In the body `list` will be the list field of the `PairwiseListMatrix` and `k` the index +over that list. Other variables are expanded in the current scope. You must not modify the +value of `k`. + +```jldoctest +julia> using PairwiseListMatrices + +julia> PLM = PairwiseListMatrix([1,2,3], false) +3×3 PairwiseListMatrix{Int64,false,Array{Int64,1}}: + 0 1 2 + 1 0 3 + 2 3 0 + +julia> @iteratelist PLM println(list[k]) +1 +2 +3 + +``` +""" +macro iteratelist(plm, exp) + esc(quote + let list = $plm.list + for k in 1:length(list) + $exp + end + end + end) +end + +""" +The macro `@iteratediag` writes a `for` loop over the `diag` field of a +`PairwiseListMatrix{T,false,VT}` but avoiding calls to `getfield` inside the loop. The first +argument of the macro is the `PairwiseListMatrix` that is going to be iterated and the +second is the body of the loop. In the body `diag` will be the diag field of the +`PairwiseListMatrix` and `k` the index over that vector. +Other variables are expanded in the current scope. You must not modify the value of `k`. + +```jldoctest +julia> using PairwiseListMatrices + +julia> PLM = PairwiseListMatrix([1,2,3], false) +3×3 PairwiseListMatrix{Int64,false,Array{Int64,1}}: + 0 1 2 + 1 0 3 + 2 3 0 + +julia> @iteratediag PLM diag[k] += 10k + +julia> PLM +3×3 PairwiseListMatrix{Int64,false,Array{Int64,1}}: + 10 1 2 + 1 20 3 + 2 3 30 + +``` +""" +macro iteratediag(plm, exp) + esc(quote + if !$hasdiagonal($plm) + local diag = $plm.diag + for k in 1:length(diag) + $exp + end + end + end) +end + +""" +The macro `@iterateupper` iterates over the upper triangular part of the +`PairwiseListMatrix` that is given as first argument. The second argument should be +`true` if the diagonal need to be included in the iteration or `false` otherwise. +The last argument is the body of the loop, where `list` is the list and diag fields of +the `PairwiseListMatrix` and `k` is the index over that `list`. +You can also use the respective `i` and `j` indexes for that position `k` in the upper +triangular part of the matrix. Other variables are expanded in the current scope. +You must not modify the values of `i`, `j` or `k`. + +```jldoctest +julia> using PairwiseListMatrices + +julia> PLM = PairwiseListMatrix([1,2,3], true) +2×2 PairwiseListMatrix{Int64,true,Array{Int64,1}}: + 1 2 + 2 3 + +julia> mat = zeros(Int, 2, 2) +2×2 Array{Int64,2}: + 0 0 + 0 0 + +julia> let mat = mat # To avoid using global + @iterateupper PLM true mat[i,j] = list[k] + end + +julia> mat +2×2 Array{Int64,2}: + 1 2 + 0 3 + +``` +""" +macro iterateupper(plm, use_diag, exp) + esc(quote + let N = $plm.nelements + local k, diag, list + if $hasdiagonal($plm) + if $use_diag + k = 0 + list = $plm.list + for i in 1:N + for j in i:N + k += 1 + $exp + end + end + else + k = 0 + list = $plm.list + for i in 1:N + for j in i:N + k += 1 + if i != j + $exp + end + end + end + end + else + if $use_diag + k = 0 + diag = $plm.diag + list = $plm.list + for i in 1:N + for j in i:N + if i != j + k += 1 + $exp + else + let list = diag, k = i + $exp + end + end + end + end + else + k = 0 + list = $plm.list + for i in 1:(N-1) + for j in (i+1):N + k += 1 + $exp + end + end + end + end + end + end) +end diff --git a/src/pairwiselistmatrix.jl b/src/pairwiselistmatrix.jl index 9e40510..763d067 100644 --- a/src/pairwiselistmatrix.jl +++ b/src/pairwiselistmatrix.jl @@ -527,9 +527,7 @@ end bc::Base.Broadcast.Broadcasted{Nothing}) where {T, D, VT} axes(dest) == axes(bc) || Base.Broadcast.throwdm(axes(dest), axes(bc)) bc_ = Base.Broadcast.preprocess(dest, bc) - apply2upper(dest; use_diag=true) do list, k, i, j - list[k] = bc_[CartesianIndex(i,j)] # slow: bc_ has a plm - end + @iterateupper dest true list[k] = bc_[CartesianIndex(i,j)] # slow: bc_ has a plm return dest end @@ -1027,7 +1025,7 @@ julia> to_table(plm) "3" "3" 0 julia> to_table(plm, diagonal=false) -3×3 Array{Any,2}: +3×3 Array{Any,2}: "1" "2" 10 "1" "3" 20 "2" "3" 30 @@ -1038,11 +1036,11 @@ function to_table(plm::PairwiseListMatrix; diagonal::Bool = true, labels = getla N = plm.nelements table = Array{Any}(undef, diagonal ? div(N*(N+1),2) : div(N*(N-1),2), 3) t = 0 - apply2upper(plm; use_diag=diagonal) do list, k, i, j - t += 1 - table[t, 1] = labels[i] - table[t, 2] = labels[j] - table[t, 3] = list[k] + @iterateupper plm diagonal begin + t += 1 + table[t, 1] = labels[i] + table[t, 2] = labels[j] + table[t, 3] = list[k] end table end @@ -1094,7 +1092,7 @@ function to_dict(plm::PairwiseListMatrix{T,D,TV}; J = Array{String}(undef, L) K = Array{T}(undef, L) t = 0 - apply2upper(plm; use_diag=diagonal) do list, k, i, j + @iterateupper plm diagonal begin t += 1 I[t] = labels[i] J[t] = labels[j] @@ -1217,9 +1215,7 @@ function DelimitedFiles.writedlm(filename::String, delim::Char = '\t', labels::Vector{String} = getlabels(plm)) where {T,D,TV} open(filename, "w") do fh - apply2upper(plm; use_diag=diagonal) do list, k, i, j - println(fh, labels[i], delim, labels[j], delim, list[k]) - end + @iterateupper plm diagonal println(fh, labels[i], delim, labels[j], delim, list[k]) end end diff --git a/test/runtests.jl b/test/runtests.jl index 1aa409c..ca6516b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -520,6 +520,9 @@ end apply2list(PLMfalse) do list, k @test(list[k] == list_values[k]) end + + @iteratelist PLMtrue Main.@test(list[k] == list_values[k]) + @iteratelist PLMfalse Main.@test(list[k] == list_values[k]) apply2diag(PLMfalse) do diag, k @test(diag[k] == 0) @@ -545,6 +548,15 @@ end apply2upper(PLMfalse, use_diag=false) do list, k, i, j list[k] = full_f[i, j] end + + @iterateupper PLMtrue true list[k] = list_values[k] + @iterateupper PLMfalse false list[k] = list_values[k] + + @iterateupper PLMtrue true list[k] = full_t[i,j] + @iterateupper PLMtrue false list[k] = full_t[i,j] + + @iterateupper PLMtrue true list[k] = full_f[i,j] + @iterateupper PLMfalse false list[k] = full_f[i,j] end @testset "IO" begin