Robust method of modifying corners of an Array

I’m trying to write a simple function that replaces the corners of an array with a value. This is what I have so far:

function replaceCorners!(a::AbstractArray{String, 2}, v::String)
    a[1, 1] = a[1, size(a)[2]] = a[size(a)[1], 1] = a[size(a)...] = v
end

I have a few questions about this problem, especially because I’m new to Julia.

  1. Is this the most “julian” way to replace the corners of an array?
  2. This method works for an array that looks like ["a" "b" "c"], but not for an array such as ["a"; "b"; "c"]. How can I make it work for arrays of any size?
  3. In the function above, I only allowed for arrays that contain strings and values that are strings - how would I go about making this applicable to any type?

Thanks in advance for your suggestions!

replaceCorners!(a::AbstractArray{T, 2}, v::T) where T

also, reuse size(a)

2 Likes

For question 3, the corredt way is to have the function be function replaceCorners!(a::AbstractMatrix{T}, v::T) where T

1 Like

Do you mind clarifying on this? From what I can tell, I am reusing size(a) in the function.

my bad, I think in this inline case size only gets evaluated once.

Here’s a simple solution for doing this in general.

firstlast(x) = first(x):length(x)-1:last(x)
function replace_corners!(a::AbstractArray, v)
    a[firstlast.(axes(a))...] .= (v,)
    return a
end
7 Likes

Are you aware that you can use end?

function repcorners!(a, v)
    a[1, 1] = a[1, end] = a[end, 1] = a[end, end] = v
end

This is less general than @mbauman’s solution, but it’s the obvious implementation of what you are doing.

Also, it is more idiomatic to write size(a, 1) than size(a)[1].

3 Likes

Thanks for the alternative solution! I’m having some trouble figuring out what exactly is happening on the third line.

With an example array like this

a = [
    12 11 10 9;
    8 7 6 5;
    4 3 2 1;
]

Running firstlast.(axes(a)) gives me (1:2:3, 1:3:4). This makes sense, but splatting that then indexing a by the result is hard to visualize. Secondly, what is the significance of using (v,)?

I actually had the following at first (before a ninja edit):

firstlast(x) = [first(x), last(x)]

That is, I just want an array with two elements. The above isn’t ideal because it’ll actually allocate an Array for every dimension. I swapped to a cleverly constructed StepRange that contains the same elements — but will probably avoid allocations entirely.

So I splat that into the array, which effectively does:

a[[1,end],[1,end],[1,end], #=etc=#]

This selects the cartesian product of all the endpoints of each dimension in the array. For a two-dimensional array, there are 4 (2*2) corners. A cube has 8 (2*2*2). And so on.

Then I broadcast the value v into all those locations I’ve selected. If v itself is broadcastable container, well, then it’ll try to match its shape with the shape of the indices I used — potentially “spreading” its values out amongst the selected locations (but more likely just throwing a shape mismatch error). Using (v,) “protects” the value inside a container I know is only one element and ensures that v itself is assigned to each and every single chosen location.

Also note that I’m not restricting the type of v at all. Upon assignment, Julia will attempt to convert v to the element type of the array for you — and it’ll happily throw an error at that point if it’s not possible.

7 Likes

FWIW, ImageTransformations has CornerIterator

2 Likes