Nice printing of some values of an array

During a simulation, it can be useful to monitor some representative values of an array (mean, max, min, median).
With a colleague, we’ve tried to write a function or macro that prints the minimum and maximum value of an array.
The idea was to pass only one argument, the array of interest. Then, the function or macro would internally retrieve the variable name, the values of interest (e.g. `minimum(x)') and print a string. The current MWE looks like this:

macro minmax(x)
    quote
        name  = $(string(x))
        var   = $(esc(x))
        info  = @sprintf("min(%05s) = %2.4e --- max(%05s) = %2.4e\n", name, minimum(var), name, maximum(var))
        print(info)
    end
end

So one can call the macro like this @minmax(x) and get, e.g.:
min( x) = 1.0000e+00 --- max( x) = 1.0000e+00

  1. Question 0: Is there already a package that provides such type of functions/macros?

  2. Question 1: That’s handy but as it’s a macro, I don’t think it’s possible to use multiple dispatch to change the formatting of number based on the type of x. Or is it possible?
    For example, it would be great to print using %d if x is an integer array.

  3. Question 2: Regarding formatting. Now lets assume I have arrays with different values, some have only positive values, some have both positive and negative values, some only negative:

# Arrays
x    = ones(10)
y    = -1*ones(10)
z    = ones(10)
z[1] = -1

# Print info
@minmax(x)
@minmax(y)
@minmax(z)

This returns an output which is all shifted because of the occurrence of minus signs:

min(    x) = 1.0000e+00 --- max(    x) = 1.0000e+00
min(    y) = -1.0000e+00 --- max(    y) = -1.0000e+00
min(    z) = -1.0000e+00 --- max(    z) = 1.0000e+00

Would there be a way to avoid this shift and get everything nicely aligned?

Any thoughts would be welcome!

A potential extension could be to also print the physical dimension, that would be cool…

If you printed a summary for all the arrays at once, the best solution would probably be to use PrettyTables.jl. But as I understand it, you have a running simulation and wish to summarize an array in each iteration. In that case, it is difficult (impossible) to align the lines when you don’t know what to the future arrays will contain (current array might contain only single-digit numbers, but the following array might contain triple-digit numbers). Nevertheless, you might have some intuition about what the value ranges are. I would avoid macros and tried something like this:

using Printf

# fmt_val is supposed to provide a string representation of `x` 
# in the desired format
fmt_val(x) = string(x)
fmt_val(x::Integer) = x < 0 ? string(x) : " " * string(x)

# This might be useful when, e.g., minimum returns single-digit integers, 
# but maximum returns triple-digit integers. In that case, you want to 
# differentiate between integers returned from minimum and maximum.
struct CustomResult{T}
    x::T
end
fmt_val(x::CustomResult{<:Integer}) = @sprintf "%05d" x.x
fmt_val(x::CustomResult{<:Real}) = @sprintf "%05.4f" x.x

custom_maximum(vals) = CustomResult(maximum(vals))

function minmax(name, vals)
    # list of values to compute together with their labels
    summaries = ["min" => minimum,
                 "max" => maximum,
                 "mymax" => custom_maximum]
    print(name, ": ")
    for (i, (label, func)) in enumerate(summaries)
        print(label, "=", fmt_val(func(vals)))
        i < length(summaries) && print(", ")
    end
    println()
end

Since it is a function, you would have to pass the variable name:

julia> minmax("x", [1, 2, 3, 54])
x: min= 1, max= 54, mymax=00054

julia> minmax("x", [1, 2.2, 3, 54])
x: min=-54.0, max=3.0, mymax=3.0000
2 Likes

Thanks a lot for the input. It’s a nice one that switches printing style based on the type, cool!
Yes, I would like to call these functions at each iteration.

I guess there will be no way to get these points at the same time:

  • pass the variable only, not also a string representing its name (because one needs a macro for this)
  • use multiple dispatch (because one needs a function for this)
  • make sure that the text is aligned (because it’s not possible)

Right?

These are not mutually exclusive. You can implement minmax as a macro and fmt_val as a function with many methods. I just prefer to avoid macros.

I don’t think you can guarantee alignment without knowing what values to expect.

You can provide a large enough width field:

using Printf
@printf("min(x) = %11.3e .... max(x) = %11.3e", extrema(rand(Int)*(rand(5) .-0.5))...)

min(x) =   -1.281e+16 .... max(x) =    4.409e+16
min(x) =   -6.228e+17 .... max(x) =    5.941e+17
min(x) =    4.455e+17 .... max(x) =    4.238e+18
min(x) =   -1.418e+18 .... max(x) =    2.560e+18
1 Like

Thanks a lot to all of you!
By combining your advices, I could reach want I wanted.

  • the macro call takes a single argument.
  • it generates the name from the variable internally
  • it changes printing style based on the type of array
  • the printed lines are nicely aligned
_minmax(name, var::Array{Int64})   = @sprintf("min(%06s) = %+011d --- max(%06s) = %+011d\n", name, minimum(var), name, maximum(var))
_minmax(name, var::Array{Float64}) = @sprintf("min(%06s) = %11.3e --- max(%06s) = %11.3e\n", name, minimum(var), name, maximum(var))

macro minmax(x)
    quote
        name  = $(string(x))
        var   = $(esc(x))
        info  = _minmax(name, var)
        print(info)
    end
end
2 Likes