Writing a block comparing multiple fields for sorting?

I’ve defined a struct with instance variables a,b,c,d all integers. I want to sort a large Array of these. This seems simple, but I’m not sure just how to write a neat and clear block to feed to sort() where field ‘a’ is compared first. If the ‘a’ fields are equal, compare ‘b’. If that’s equal, … you get the idea. I need to sort fields in the order a,b,c,d in one place, and d,c,b,a in another. And b,d,c,a elsewhere.

Using the <=> operator seems sensible but I don’t know how to write a hierarchy of comparisons, to pass on the == case of one field on to a comparison of another. Can it fit neatly in a line or two?

And what if instead of a struct I make it a regular class - would that matter?

arr.sort_by { |x| {x.a, x.b, x.c, x.d} }


Ah, that looks elegant and easy! I wouldn’t have thought of that, but then, I’ve been brainwashed by too many previous more primitive languages.

+100 points to Crystal for being a great language!


When the sorting behaviour is inherent to the type, it would be better to define a <=> comparison method to make it reusable. Otherwise you’d have to duplicate the custom sort_by logic every time you need to sort instances of that type.

You can take a look at implementations in stdlib, for example SemanticVersion.
A one-liner would delegate to tuple comparison: {a, b, c, d} <=> {other.a, other.b, other.c, other.d}.

The struct is not a sortable type, but a lump of information with no concept of ordering, no way to model as a lattice or anything. I want to sort it according to one way just once in one place, another way just once in another place. These sortings are useful each in their place, but have no meaning otherwise. Also, I want to sort in ways according to a,b,c,d here, according to a+b,b-d,c+d there. So, defining a <=> operator wouldn’t make much sense. In C++, I’d give sort() a lambda custom written in each place I want to sort.

Sorting by a tuple or array is cool! Not only does it solve my problem (or at least get me to the next problem), but I’m thinking of other ways this can be useful. Stuff you can’t do in C++, or maybe I just haven’t run into yet.

I had the same feeling when I’ve discovered this feature in someone’s github issue answer, lol.

1 Like