Howto correctly (hijack) fill an array with formula over indices?

It seems fill is built to handle static init value only.
I am trying to write something like this

A = reshape(1:6,(2,3))
fill((i,j)->10i+j,A)

# or, if you prefer
fill(A) do i,j; 10i+j end

For now, i have got this one that pass the test

Base.fill(f::Function, A) = map(x->f(Tuple(x)...),CartesianIndices(A)) # todo fill!

using Test
@test fill((i,j)->10i+j,A) == [11 12 13; 21 22 23]

Is there a simpler julian way to do it (especially a builtin in base or stdlib i have missed)?
Is there any pro or con that is better to know with this kind of trick ?

Of course we could use nested for. But why should we not use high-order function ?

Replying to myself

I have got this one too (vectorization style)

Base.fill(f::Function, A) = Base.splat(f).(Tuple.(CartesianIndices(A)))

A = reshape(1:6,(2,3))
@test fill((i,j)->10i+j,A) == [11 12 13; 21 22 23]

Not clear yet to me which one is idiomatic …

Perhaps you’re looking for an array comprehension:

julia> [10i+j for i=1:2, j=1:3]
2×3 Array{Int64,2}:
 11  12  13
 21  22  23

That’s was the third form i was coming back to append :slight_smile:

A kind of pivoted nested for. I admit it’s very clean for oneliner formula.
However

[ (u=compute_a_long_formula(i,j); v=compute_another_weaponized_math(u); so_finally_got(v)) for i=1:2, j=1:3]

can become tricky to read, when

fill(A) do i,j;
  u = compute_a_long_formula(i,j)
  v = compute_another_weaponized_math(u)
  so_finally_got(v)
end

naturally preserve readability.
I was just hoping that was without deserving internals law of good taste and performance.

Don’t define that method for fill, it is type piracy.

IMO array comprehension, suggested by @c42f, is your current best option.

There is also Iterators.product. See

and for some suggested changes

Having fill(f::Function, dims) for use with do syntax does make some sense. It would technically be a breaking change because it would change the meaning of the following:

julia> fill(sum, (2,3))
2×3 Array{typeof(sum),2}:
 sum  sum  sum
 sum  sum  sum

However, I’m not sure what one could possibly use such an array for (it can only hold objects of the singleton type typeof(sum))!

Keep in mind that in Julia every statement is an expression, so you can create a somewhat readable version of the array comprehension using a block:

A = [begin
        u = compute_a_long_formula(i,j)
        v = compute_another_weaponized_math(u)
        so_finally_got(v)
     end
     for i=1:2, j=1:3]

Personally I find this kind of odd looking though.

1 Like

So one more form

fil2(f::Function,a::AbstractArray) = Base.splat(f).(Iterators.product(axes(a)...))
@test fil2((i,j)->10i+j, [1 2 3; 4 5 6]) ==  [11 12 13; 21 22 23 ]

Right. I have forgotten to review the iterators funcs. It’s quite close to CartesianIndex. May be too close to have two.

PRO
CartesianIndex pops here and there (IndexCartesian, CartesianIndex, CartesianIndices, view etc.). Iterators.product seems less used.

CON
Iterators.product may be faster by skipping a call to collect.


I disagree. I knew type piracy. It’s a bad practice that had to be fight.
Preventing it by dispatching on func above any, any, any … args in base, is not better.
It’s multidispatch piracy.

There are too much functions foo(x::Any, y::Any) that do rebranch to foo(::Bar, ::Any), foo(::Any, ::Baz), foo(::Qux,::Quux) by bypassing multidispatch via hard-coded spaghetti dispatch.

In very few line, one will inject very hard to grasp, test, optimize, stabilize and check code. This may has been useful to deliver v1 on time. I hope change will come afterward.


Thanks for the good links. Now i need more time to digest them too :slight_smile:

There may be some breaking change but i can not see any major usage of this form too. On the content, Base.fill on function rechecked with compatible type may rebranch to a dynamic fill.

function Base.fill(f::Function, a::AbstractArray{T,N}) where {T,N} =
    isempty(methods(f,NTuple{N,Int})) && error("f") # todo handle IndexStyle
    do_fill_with_formula(...)
end

is correct, i know.

However what make julia my personal preference is the “talk like python, run like c, think like lisp” motto.
To which i append my personal opinion that is to not loose the zen of python in translation.
And too much of those forms hurts it quite a bit imho. so-so.

How about this for zen?

A = let f(i, j) = so_finally_got(compute_another_weaponized_math(compute_a_long_formula(i,j)))
    [f(i, j) for i in 1:2, j in 1:3]
end
2 Likes

A bit hard to read for me. (Readability counts damaged).
3 instruction per lines, nearly identical to

Base.fill(f::Function, A) = map(x->f(Tuple(x)...),CartesianIndices(A)) # todo fill!

Let’s say #ops is ok. What can be improve ?

  • Naming - we can shorten them. Those were mine and quite ridiculous.o:
    Expert domains bring their own and they tend to prefer short ones in julia.
    That may works

  • Frequence of use of primitive / patterns. Hoping this topic will help…

On a structural viewpoint, I will prefer this one

A = let f(i, j) = 
        compute_a_long_formula(i,j) |>
        compute_another_weaponized_math |>
        so_finally_got;
    [f(i,j) for i in 1:2, j in 1:3]
end

But i consider julia is better equipped to handle vectorization (v13n) than higher-order functional programming (hours in debugging already lost). So the v13n form with do is what works better for me.

Sorry, I don’t understand you.