# Sorting arrays of structures

Dear All,

Could you, please, give me a hand in a sorting problem:

``````mutable struct Vox
x::Int32
y::Int32
z::Int32
end

Voxels=Vector{Vox}(undef, 0)
#loop starts here to fill the arrays of Vox structures
#loop ends here

sort!(Voxels, by=expression, rev=true)
#e.g., expression=getfield.(tmp, :radiusR) or any other way to extract this vector of data
``````

I get an error: ERROR: MethodError: objects of type Array{Float64,1} are not callable
Use square brackets for indexing an Array.
I tried many ways around with sort function, but finally only one worked as described below.

Now, if i substitute the above-mentioned array of structures with a tuple of the form:
`v=Array{Tuple{Float64,Int,Int,Int}}(undef,0); loop to fill the array: push!(v,(computeRadius[j,k,i], j, k, i)); sort!(v, by=first, rev=true)`
and i get what i want. Is there a way to sort original array of structures in a fast and elegant fashion?

By the way, youâ€™ll be more likely to get useful help if you post code that actually works (or at least actually reproduces the error youâ€™re asking about). Currently, your code relies on `computeRadius`, `i`, `j`, `k`, and `expression`, which are all undefined, so we have to guess what you mean in order to be helpful.

But I think what you want is something like:

``````julia> sort!(Voxels, by = v -> v.radiusR, rev=true)
2-element Array{Vox,1}:
Vox(2.0, 2, 3, 4)
Vox(1.0, 2, 3, 4)
``````

The `by` argument takes a function, so in this case Iâ€™ve used an anonymous function.

4 Likes

I would normally just define `isless(::Vox, ::Vox)`. Then any kind of sorting problem should just work without needing the anonymous function.

``````julia> mutable struct Vox
x::Int32
y::Int32
z::Int32
end

julia> import Base: isless

isless (generic function with 67 methods)

julia> xs = [Vox(x, 1,2,3) for x = 10.0:-1.0:1.0]
10-element Array{Vox,1}:
Vox(10.0, 1, 2, 3)
Vox(9.0, 1, 2, 3)
Vox(8.0, 1, 2, 3)
Vox(7.0, 1, 2, 3)
Vox(6.0, 1, 2, 3)
Vox(5.0, 1, 2, 3)
Vox(4.0, 1, 2, 3)
Vox(3.0, 1, 2, 3)
Vox(2.0, 1, 2, 3)
Vox(1.0, 1, 2, 3)

julia> sort!(xs)
10-element Array{Vox,1}:
Vox(1.0, 1, 2, 3)
Vox(2.0, 1, 2, 3)
Vox(3.0, 1, 2, 3)
Vox(4.0, 1, 2, 3)
Vox(5.0, 1, 2, 3)
Vox(6.0, 1, 2, 3)
Vox(7.0, 1, 2, 3)
Vox(8.0, 1, 2, 3)
Vox(9.0, 1, 2, 3)
Vox(10.0, 1, 2, 3)
``````
4 Likes

Thank you, Raf! This also worked perfectly.
However, i do have some additional question if you do not mind:

Just to understand Julia better, did you do here something we could call overloading isless and adding sorting as a private function of the Vox class (from C++ perspective)?

My apologies, I actually tried to describe the problem as brief as possible, the code to fill in the array has nothing to do with the sorting problem. But i shall consider copying the whole code next time if this is more usefulâ€¦

Thank you very much, this solution worked just fine!

This is multiple dispatch at work. You created your own type (Vox), and overloaded the definition of the isless function to work with elements from your type. I donâ€™t know how that compares to C++

1 Like

That is not really necessary. The thing to do is to make a minimal working example (or non-working, as the case may be), that is, make an example which is as simple as possible, but demonstrates what you want help with.

Hereâ€™s a MnWE, borrowing from @Raf and you:

``````mutable struct Vox
x::Int32
y::Int32
z::Int32
end

xs = [Vox(x, 1,2,3) for x = 10.0:-1.0:1.0]
``````

together with the error message.

BTW: are you sure you need `Vox` to be mutable? Just curious, but small structs like these are often good candidates to be immutable, potentially with much better performance.

2 Likes

For homogeneous vectors, this really works entirely the same way as in C++: You pass a singleton object (`Function` object) in the by parameter that does the comparisons; dispatch is at compile time and hence comparisons can be inlined. Only that in julia, there is a `Base.isless` that is used by default, and holds a julia â€śFunctionâ€ť, which is basically a bunch of compiled julia methods (C functions); when your `sort(arr::Vector{T})` is compiled (jitted), then the correct method is selected.

You donâ€™t need to have public/private/virtual isless methods attached to your type in C++ either.

That being said, I prefer the julia syntax for this kind of thing. For heterogenous arrays, Iâ€™d get a headache trying to get this right in C++.

1 Like

Not much to add. But writing a minimum working example lets other people run your code, understand the problem, and make changes without doing too much work themselves.

In terms of the â€śoverloadingâ€ť in Julia it is best described as adding a method to the Base function `isless` for your custom type Vox. Multiple dispatch will choose your `isless` method from the 67 possible methods when `isless(::Vox, ::Vox)` is called.

1 Like

Thanks for this useful example. Suppose we wanted to compare Vox objects by radius, as you have done, but then break ties by comparing x, then y, then z values. Is there an idiomatic way of doing this?

Extend `isless` to incorporate the tie breaking.

You can either implement that tie-breaking inside your `isless` implementation, or you can rely on the fact that `Tuple`s already support this kind of sorting behavior. For example, we can use this to sort a `Person` struct by last name, then by first name:

``````julia> struct Person
firstname::String
lastname::String
age::Int
end

julia> people = [Person("John", "Smith", 20), Person("Jane", "Smith", 22), Person("John", "Aaronson", 23)]
3-element Array{Person,1}:
Person("John", "Smith", 20)
Person("Jane", "Smith", 22)
Person("John", "Aaronson", 23)

julia> sort(people, by = p -> (p.lastname, p.firstname))
3-element Array{Person,1}:
Person("John", "Aaronson", 23)
Person("Jane", "Smith", 22)
Person("John", "Smith", 20)
``````
4 Likes

Which of course works the same when extending `isless`

``````julia> import Base: isless

julia> isless(lhs::Person, rhs::Person) = isless((lhs.lastname, lhs.firstname), (rhs.lastname, rhs.firstname))
isless (generic function with 42 methods)

julia> people = ...

julia> sort(people)
3-element Array{Person,1}:
Person("John", "Aaronson", 23)
Person("Jane", "Smith", 22)
Person("John", "Smith", 20)
``````
2 Likes

Thanks to you both, @rdeits and @tamasgal! I think this will turn my ten lines of horrible code into a one-liner that is much clearer.