# Generalizing functions to accept inputs with 1 or 2 (or with 2 or 3) dimensions

**URL:** <https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317>\
**Category:** New to Julia\
**Tags:** function\
**Created:** [October 30, 2020, 4:59pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317 "2020-10-30T16:59:53Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 4:59pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/1 "2020-10-30T16:59:53Z")

</div>

I wish to create a function which can accept one input and perform calculations, or can accept multiple inputs, choose the best one, and then perform calculations. I’m not sure how to approach it. I originally thought that I would keep the “options” in the last dimension of an `AbstractArray`. A toy example is below, but I cannot get `sort` to work. It gives a method error when I pass a `Vector` or a `Range`. I next thought about writing one method for `AbstractVector`s and another for `AbstractArray`s. However, I do not think that approach would extend to higher dimensions, because I do not know how I would call different methods on a 2D vs 3D array. Finally, I thought about creating a vector of arrays where the outer vector construct would contain the “options”, but that may make the code more complex than it needs to be. Is there a preferred way to handle this situation? (Coding critiques are also welcome.)

```julia
function f(x)
    il=size(x,1)
    jl=size(x,2)

    #sort!(x,dims=1) # this works for f(b), but breaks for f(a) and f(c).

    y=Array{typeof(x[1])}(undef,il)
    for i in 1:il
        y[i]=sum(x[i,:])
    end

    (minsum,jminsum)=findmin(sum(x,dims=1))
    z=x[:,jminsum]

    return y,z
end

a=[4,2,3,1]
b=[[5,8,6,7] [11,10,9,12]]
c=10:-2:0

f(a)
f(b)
f(c)

```

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [October 30, 2020, 5:36pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/2 "2020-10-30T17:36:39Z")

</div>

I’m not sure of understanding the problem entirely, but with respect to this:

> [@Nathan\_Boyer](#):
>
> I cannot get `sort` to work. It gives a method error when I pass a `Vector` or a `Range`. I next thought about writing one method for `AbstractVector` s and another for `AbstractArray` s. However, I do not think that approach would extend to higher dimensions, because I do not know how I would call different methods on a 2D vs 3D array.

`sort` (and `sort!`) methods for 2D, 3D, and N-D arrays are just the same, so that should not be a problem, I think:

```julia
julia> x = cat(rand(3,2), rand(3,2), dims=3)
3×2×2 Array{Float64,3}:
[:, :, 1] =
 0.460061 0.727659
 0.155417 0.241911
 0.148384 0.285468

[:, :, 2] =
 0.782055 0.269738
 0.126793 0.786585
 0.851543 0.56357

julia> sort!(x, dims=1)
3×2×2 Array{Float64,3}:
[:, :, 1] =
 0.148384 0.241911
 0.155417 0.285468
 0.460061 0.727659

[:, :, 2] =
 0.126793 0.269738
 0.782055 0.56357
 0.851543 0.786585

```

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [October 30, 2020, 5:47pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/3 "2020-10-30T17:47:57Z")

</div>

> [@Nathan\_Boyer](#):
>
> I originally thought that I would keep the “options” in the last dimension of an `AbstractArray` .

That seems like a pretty unusual way of doing things in Julia, and I think that’s part of why you’re having trouble expressing this cleanly in code.

I think it’s worth spending a little bit of time thinking about what the nouns in your problem are and then figuring out the right set of verbs to operate on them.

For example, if a single input to your problem contains a vector of data and a boolean option, then I would start by creating a `struct` to hold precisely that:

```julia
struct Input
  data::Vector{Float64}
  option::Bool
end

```

Suddenly the two cases become much clearer: you either have an `Input` or a `Vector{Input}`. That’s it. Now your function could look like:

```julia
function perform_calculations(input::Input)
  do_stuff(...)
end

function select_best(inputs::AbstractVector{Input})
  # iterate over `inputs`, find the best one, and return it
end

function my_function(inputs::AbstractVector{Input})
  perform_calculations(select_best(inputs))
end

function my_function(input::Input)
  perform_calculations(input)
end

```

We’ve accomplished a bunch of things all at once: First, the core `perform_calculations` function no longer has to worry about whether it’s in the multi-input or single-input case. Second, the selection of the best input is a nice standalone function that we can test and iterate on. And third, the two cases (multiple inputs or a single input) are easily separated by multiple dispatch on `my_function`.

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 5:56pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/4 "2020-10-30T17:56:12Z")

</div>

Yes, the issue is that `sort(x,dims=1)` will not work for the 1D case.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [October 30, 2020, 6:07pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/5 "2020-10-30T18:07:51Z")

</div>

What Nathan means is that:

```julia
julia> a = [3, 2, 1];
julia> sort(a)
3-element Array{Int64,1}:
 1
 2
 3

julia> sort(a; dims = 1)
ERROR: MethodError: no method matching sort!(::Array{Int64,1}; dims=1)
[...]

julia> b = [3 2 1];
julia> sort(b)
ERROR: UndefKeywordError: keyword argument dims not assigned
[...]

julia> sort(b; dims = 1)
1×3 Array{Int64,2}:
 3 2 1

```

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [October 30, 2020, 6:09pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/6 "2020-10-30T18:09:19Z")

</div>

Then you can make and use a function that generalizes for both cases:

```julia
sortvectororarray!(x::AbstractVector; kwargs...) = sort!(x)
sortvectororarray!(x::AbstractArray; kwargs...) = sort!(x; kwargs...)

```

Depending on how you use it, it might be safely used without producing type instabilities.

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 6:11pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/7 "2020-10-30T18:11:09Z")

</div>

That makes sense. My Matlab habits are dying hard. I am used to just adding dimensions to my array when I need to store more data and adding more lines to my code when I need to add more operations. I’ve never used `struct`s, but it is probably time to start. Although for my current problem the input is just a vector.

I did oversimplify a bit since I need to perform most of the calculations on all of the inputs before I know which one is best. Currently, I am keeping track of the proper indices to spit back out after I find the best one. However, based on your outline, I think it may be cleaner to just call the function again once I know which input to use.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [October 30, 2020, 6:18pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/8 "2020-10-30T18:18:17Z")

</div>

Not the best solution. The first method (`AbstractVector`) should remove the `dims` of the `kwargs` if it is present and pass the rest to `sort!`, otherwise it will be impossible to access any other options of `sort!`.

---

<div class="post-metadata">

**Author:** ![heliosdrm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heliosdrm/32/3851_2.png) [@heliosdrm](https://discourse.julialang.org/u/heliosdrm)\
**Post date:** [October 30, 2020, 6:23pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/9 "2020-10-30T18:23:40Z")

</div>

True, but I don’t mean `sortvectororarray!` to be of general use, but an _ad-hoc_ function, specific for that problem. So, if no other options of `sort!` have to be used, there is no need to make them available.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 30, 2020, 6:30pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/10 "2020-10-30T18:30:39Z")

</div>

> [@Nathan\_Boyer](#):
>
> I wish to create a function which can accept one input and perform calculations, or can accept multiple inputs, choose the best one, and then perform calculations.

I am not sure if this is what you want, but this example is, literally, what conforms to that description (here the “best” input is the smallest of the numbers, and the “best” is multiplied by 10 at the end):

```julia
julia> function f(args...)
         best = args[1]
         for i in 2:length(args)
           if args[i] < best
             best = args[i]
           end
         end
         return 10*best
       end
f (generic function with 1 method)

julia> f(1)
10

julia> f(1,2)
10

julia> f(0.5,1,2)
5.0

```

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [October 30, 2020, 6:33pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/11 "2020-10-30T18:33:49Z")

</div>

> [@Nathan\_Boyer](#):
>
> I’ve never used `struct` s, but it is probably time to start.

Yes! Do it! 🙂

The ability to structure your data with little or no cost to performance is one of Julia’s superpowers, and it’s something you simply cannot do in MATLAB or Python.

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 6:36pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/12 "2020-10-30T18:36:09Z")

</div>

I should also point out that there are other inputs to my function which will be constant for the set of input “options”. That is, it may look like `f(x::Int, y::Vector, z::Array)` where `x` and `y` will apply to every column of `z`.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [October 30, 2020, 6:45pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/13 "2020-10-30T18:45:43Z")

</div>

You mean something like that (supposing you want to change the parameter `z`)?

```julia
julia> function f(x::Int, y::Vector, z::Array)
           xy = x .* y
           (col -> col .+= xy).(eachcol(z))
           z
       end
julia> x = 1;
julia> y = [0, 1, 2];
julia> z = [3 3 3; 2 2 2; 1 1 1]
3×3 Array{Int64,2}:
 3 3 3
 2 2 2
 1 1 1

julia> f(x, y, z)
3×3 Array{Int64,2}:
 3 3 3
 3 3 3
 3 3 3

julia> z
3×3 Array{Int64,2}:
 3 3 3
 3 3 3
 3 3 3

```

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 30, 2020, 6:55pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/14 "2020-10-30T18:55:28Z")

</div>

> [@Nathan\_Boyer](#):
>
> I should also point out that there are other inputs to my function which will be constant for the set of input “options”. That is, it may look like `f(x::Int, y::Vector, z::Array)` where `x` and `y` will apply to every column of `z` .

With that syntax you could use the first parameters as the “non-variable” ones.

```julia
julia> function f(x :: Int, y :: Vector, args...)
         z = Vector{Float64}[]
         for i in 1:length(args)
           @. args[i] = args[i] + x
           push!(z,args[i]*y)
         end
         z
       end
f (generic function with 1 method)

julia> f(1, rand(3), rand(3,3), rand(3,3))
2-element Array{Array{Float64,1},1}:
 [2.1299518632077437, 2.438409415790726, 3.321686548573605]
 [3.1192576982367894, 2.5495899125063506, 2.4974216223219736]

```

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 7:08pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/15 "2020-10-30T19:08:07Z")

</div>

I just meant that I have other parameters that need to be kept alongside the main input options.

For @lmiq’s code, I think `f(x,y,args...)` works fine as long as I pass a `Vector` of `Vectors` as `args`. _(Edit: This method expects one or multiple `Vector` arguments which then are then slurped into a `Tuple`.)_

For @rdeits’s code, I wasn’t sure whether to pass the other parameters to each function along with `input` and `inputs`, or whether they should be part of the `Input` struct (even though they will not change for a given set).

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 30, 2020, 7:25pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/16 "2020-10-30T19:25:46Z")

</div>

> [@Nathan\_Boyer](#):
>
> `Vector` of `Vectors`

If you have that already it does not make much sense to use the `...` notation in your function, it makes more sense to just accept that vector of vectors as the third variable. I think it is more reasonable in general to define one method that operates on that vector of vectors taking care of the dimensions properly than to generalize the function definition such that one method specializes to each number of arguments.

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 7:44pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/17 "2020-10-30T19:44:26Z")

</div>

Oh, I understand now. The `...` in your code is slurping the arguments up into a `Tuple`. I was thinking it was splatting when I wrote my last post. Are you saying it would be better to require a `Vector` of `Vector`s rather than slurping the remaining arguments then?

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [October 30, 2020, 7:53pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/18 "2020-10-30T19:53:44Z")

</div>

I think it is a more controlled alternative. Probably the code is easier to maintain and bugs are easier to find if your function can only accept one type of argument, and the same number of arguments always.

What I said before (that specialized methods are generated for each number of arguments) appears to be wrong, only one method remains which operates on a tuple always. That is ok if you define the type of every tuple element, with, for example:

```julia
julia> g(x :: Float64 ...) = x
g (generic function with 1 method)

julia> g(1.)
(1.0,)

julia> g(1.,2.)
(1.0, 2.0)

julia> g(1.,3)
ERROR: MethodError: no method matching g(::Float64, ::Int64)

```

otherwise you might get type instabilities probably. If using this, passing the vector of vectors, or many arguments, is probably equivalent.

Actually the methods specialize depending on what you do with the arguments in the function, which is pretty smart:

```julia
julia> f(x...) = 2*x[1]
f (generic function with 1 method)

julia> @code_typed f(1,2)
CodeInfo(
1 ─ %1 = Base.getfield(x, 1, true)::Int64
│ %2 = Base.mul_int(2, %1)::Int64
└── return %2
) => Int64

julia> @code_typed f(1,"a")
CodeInfo(
1 ─ %1 = Base.getfield(x, 1, true)::Int64
│ %2 = Base.mul_int(2, %1)::Int64
└── return %2
) => Int64

julia> @code_typed f(1,2,3)
CodeInfo(
1 ─ %1 = Base.getfield(x, 1, true)::Int64
│ %2 = Base.mul_int(2, %1)::Int64
└── return %2
) => Int64

```

Note that the fact that the second argument is a string does not affect the code generated, and the fact that the third call has three arguments neither.

---

<div class="post-metadata">

**Author:** ![Henrique\_Becker](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/henrique_becker/32/15443_2.png) [@Henrique\_Becker](https://discourse.julialang.org/u/Henrique_Becker)\
**Post date:** [October 30, 2020, 10:10pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/19 "2020-10-30T22:10:45Z")

</div>

If you think convenience is the priority, then the modified version of my function below can take any number of matrices or vectors (because `eachcol` works on `Vector`s too)

> [@Henrique\_Becker](#):
>
> ```julia
> using Base.Iterators
> function f(x::Int, y::Vector, zs...)
> xy = x .* y
> (col -> col .+= xy).(flatten(eachcol.(zs)))
> end
> 
> julia> f(1, [0, 1, 2], [2, 1, 0], [2 2 2; 1 1 1; 0 0 0])
> 4-element Array{SubArray{Int64,1,Array{Int64,2},Tuple{Base.Slice{Base.OneTo{Int64}},Int64},true},1}:
> [2, 2, 2]
> [2, 2, 2]
> [2, 2, 2]
> [2, 2, 2]
> 
> ```

---

<div class="post-metadata">

**Author:** ![Nathan\_Boyer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nathan_boyer/32/14825_2.png) [@Nathan\_Boyer](https://discourse.julialang.org/u/Nathan_Boyer)\
**Post date:** [October 30, 2020, 11:46pm UTC](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317/20 "2020-10-30T23:46:10Z")

</div>

To add another twist, the `x::Int` argument is currently optional i.e. `x::Int=3`. I don’t imagine there is a way to use both optional arguments and argument slurping simultaneously?

[Next page](https://discourse.julialang.org/t/generalizing-functions-to-accept-inputs-with-1-or-2-or-with-2-or-3-dimensions/49317.md?page=2)
