How to repeat the format in @printf?

Sorry for another stupid question. Not proud of asking these kind questions :sweat_smile:.
I am sure it is stupid, however I searched google and stack overflow but still not sure how to do this.

The question is very simply,

using Printf
@printf("%9.3f %9.3f %9.3f %9.3f",1.0,2.0,3.0,4.0)

I need to display 4 numbers from 1.0 to 4.0 for example. Now, do I have to manually put %9.3f for four times in the fmt?

In Fortran I can do things like

4(9.3f)

So that I do not need to put 9.3f 4 times.

In Julia, or in printf, is there a similar way so that I do not need to repeat 9.3f 4 times?

Thank you very much in advance!

2 Likes

In Julia 1.6 you can use Printf.Format, and then Printf.format instead of the macro:

julia> f = Printf.Format("%9.3f "^4);

julia> Printf.format(f, 1,2,3,4)
"    1.000     2.000     3.000     4.000 "

julia> Printf.format(stdout, f, 1,2,3,4)
    1.000     2.000     3.000     4.000 

(Well, there is an unwanted trailing space in this example, but this can be easily worked around with other ways of making the format string.)

10 Likes

Thank you so much! That is so cool!
Nice, this should be way to go!
This is similar with what is in Fortran, like, I can define a format first (with variable in it), like

write(fmt_muk_iter, '(a, i0, a)') '(''Mu_k ='',t20,',kmixin,'(  f12.5,'' +-'',f12.5,1x,''|''   ))' 

Then I can use the format fmt_muk_iter that I defined to do output

write(6,fmt_muk_iter) (valmusigma(k,2),errormusigma(k,2),k=1,kmix)   

What you said looks similar!

The @sprintf macro produces a string (instead of printing it), and then you can use something like this:

julia> using Printf

julia> x = rand(5);

julia> println([@sprintf(" %5.2f",x) for x in x]...)
  0.22  0.51  0.82  0.49  0.90


I think this is less ā€œefficientā€, but for printing a bunch of tables in non-critical code thatā€™s practical.

4 Likes

A workaround I used to use for this before discovering the solution suggested by @heliosdrm. This is probably not suggested, but is very convenient for code interpolation in macros.

julia> using Printf

julia> @eval @printf($("%9.3f "^4), 1.0, 2.0, 3.0, 4.0)
    1.000     2.000     3.000     4.000 
2 Likes

The disadvantage of using @eval is that it evaluates the expression in the global scope, so itā€™s not great for using in functions etc.

1 Like

Yes, thatā€™s true. It would be nice to have a macro which achieves similar functionality, i.e just evaluates only the code inside $(...). I donā€™t think that would be hard. Atleast for me, I have needed to use @eval for this purpose frequently for evaluating some code which I need to pass to another macro. But I havenā€™t seen this proposed anywhere.

1 Like

@heliosdrm, that is really nice.
We can then even broadcast Printf to arrays using your solution:

v = rand(10)
f = Ref(Printf.Format("%5.2f"));
Printf.format.(f,v)
4 Likes

Also, simply map:

map(x->@printf("%9.3f ",x), (1.0,2.0,3.0,4.0));
    1.000     2.000     3.000     4.000
7 Likes

One more way with piping:

(1.0,2.0,3.0,4.0).|>(x->@sprintf("%9.3f",x))|>join
"    1.000    2.000    3.000    4.000"
2 Likes

I like this way as well:

julia> using Printf

julia> myformat(x) = @sprintf("%5.2f",x)
myformat (generic function with 1 method)

julia> x = rand(3);

julia> println(myformat.(x)...)
 0.15 0.68 0.58

5 Likes

A quick thing, is it possible to print these stuff in a txt file?

I tried things like

f = Printf.Format("%9.3f"^4)
io = open("myfile.txt", "w")
for i in 1:10
    @printf(io, f, 1,2,3,4)
end
close(io)

But it does not work.

I expect to have a file called myfile.txt in which it should have such 5 lines,

1.000     2.000     3.000     4.000
1.000     2.000     3.000     4.000
1.000     2.000     3.000     4.000
1.000     2.000     3.000     4.000
1.000     2.000     3.000     4.000

Thank you!

Note that if you are writing tabular data you can just use CSV.jl and Dataframes. Writing and reading text files can be quite complicated (lots of corner cases) so itā€™s better to use a library that is well tested.

3 Likes

@CRquantum, you could write like this (added carriage return for better formatting):

f = Printf.Format("%9.3f"^4*"\n")
io = open("myfile2.txt", "w")
for i in 1:10
    Printf.format(io, f, 1,2,3,4)
end
close(io)
1 Like

Thank you very much!

One more question, in the exactly the same example in the quote, if I put 1, 2, 3, 4 as an array a

a = [1.0, 2.0, 3.0, 4.0]

Then

Printf.format(io, f, a)

give ma an error.

Is there a way, exactly the same as this example, just that I could print array a instead of manually inputting 1,2,3,4 here?

This could be very useful because in the code it was very likely that we need to print some array with certain format.

In this case, have you tried the broadcasting solution above but without Ref and replacing by the longer formatting sequence?

Thank you very much!
Yes I tried,

a = [1.0, 2.0, 3.0, 4.0]
f = Ref(Printf.Format("%5.2f"));
str = Printf.format.(f,a)

This gives me a 4-element Vector{String} which should be correct. However, when I write to a file by,

io = open("fileout.txt", "a") 
write(io, str)
close(io)

It gives an error

`write` is not supported on non-isbits arrays

It seems write can only operator on one string? I am sure write can handle an array but I am not sure how to do it.

@CRquantum, you can add join:
(using also join trick from @gustaphe):

a = [1.0, 2.0, 3.0, 4.0]
f = Ref(Printf.Format("%5.2f"));
open("fileout0.txt", "a") do io
    join(io, Printf.format.(f,a))
end
2 Likes

Now I want a join(io::IO, f::Format , x, delim) methodā€¦

1 Like

join takes iterables, so something like

let f = Printf.Format("%5.2f")
    join(io, (Printf.format(f, a) for a in a))
end

should work fine.