Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Using Subtraction To Power The Array Sort Comparison Operator In Lucee CFML 5.2.9.31

By Ben Nadel on
Tags: ColdFusion

Yesterday, while looking at generating color histograms using GraphicsMagick and Lucee CFML, I had to sort of an Array of colors based on their frequency distribution within an image. To do this, I created a sort operator that used a single Subtraction expression to calculate the comparison result. I'm not sure that I've ever done this before; so, I wanted to take a moment and document this maths-based approach using Lucee CFML 5.2.9.31.

ASIDE: I'm looking at this in ColdFusion code since that's what I was using yesterday; but, to be clear, this is just the way that sort comparison operators work in most languages. As such, this would also apply to, for example, JavaScript's Array.prototype.sort method.

In many places that a .sort() comparison operator is discussed, it is often demonstrated using -1, 0, and 1 as the possible operator outcomes. Meaning, given two items, a and b, returning the aforementioned values carries the following connotation:

  • -1 - Value a should be sorted before value b.
  • 0 - Value a and value b are equivalent.
  • 1 - Value a should be sorted after value b.

Because of this, I often code my .sort() comparison operators to explicitly return -1, 0, or 1. For example, if I wanted to sort a collection of strings based on their length, I might do the following:

<cfscript>

	values = [
		"asdlfkj",
		"oweiru",
		"aldkfjlakjflajsdfljalsfjlfl",
		"xzmcn",
		"lakdfjlakjfl",
		"mlkjwler",
		"adf",
		"lkasdjflajdla",
		"cvuoixcviou"
	];

	// Sort the collection of values based on the LENGTH of each value.
	values.sort(
		( a, b ) => {

			var aLength = a.len();
			var bLength = b.len();

			if ( aLength < bLength ) {

				return( -1 );

			} else if ( aLength > bLength ) {

				return( 1 );

			} else {

				return( 0 );

			}

		}
	);

	dump( values );
	
</cfscript>

As you can see, I'm comparing the length of each item in the compare-operator function and then explicitly returning one of the static values. And, when we run this ColdFusion code, we get the following browser output:

An array of string values sorted by length using .sort() function in Lucee CFML.

But, the .sort() operator isn't limited to these three return values. Really, the .sort() algorithm is looking for these three general outcomes:

  • Less than zero.
  • Zero.
  • Greater than zero.

It just so happens that -1 is less than zero; and, 1 is greater than zero; that's why those values work in a comparison operator. But, really, there's nothing special about -1 and 1.

Given this broader perspective, we can greatly simplify our .sort() comparison function using a single Subtraction expression based on the length of each value:

<cfscript>

	values = [
		"asdlfkj",
		"oweiru",
		"aldkfjlakjflajsdfljalsfjlfl",
		"xzmcn",
		"lakdfjlakjfl",
		"mlkjwler",
		"adf",
		"lkasdjflajdla",
		"cvuoixcviou"
	];

	// Sort the collection of values based on the LENGTH of each value.
	values.sort(
		( a, b ) => {

			return( a.len() - b.len() );

		}
	);

	dump( values );
	
</cfscript>

If we run this ColdFusion code, we get the same output. Let's look at why, using some example values. Given a: "12345" and b: "12345678":

return( "12345".len() - "12345678".len() )

... which gives us:

return( 5 - 8 )

... which evaluates to:

return( -3 )

So, when the length of a is less than the length of b we end up with a result that is less than zero. Conversely if we have a: "12345678" and b: "12345":

return( "12345678".len() - "12345".len() )

... which gives us:

return( 8 - 5 )

... which evaluates to:

return( 3 )

So, when the length of a is greater than the length of b we end up with a result that is greater than zero.

When we stop thinking about the sort comparison in terms of static values and, instead, think about the result as a set of ranges, our logic can become much more concise. Of course, there is always tension between "concise" and "readable". My first example with explicit if statements is longer; but, it demonstrates clear intent. My second example, on the other hand, is much shorter; but, doesn't really express any intent. As such, I would almost certainly include a comment regarding my intent when using the Maths-based approach:

// Shorter values should be sorted before longer values.
return( a.len() - b.len() );

Now, the next engineer to read this code should be able to understand what the .sort() is doing even if the Maths is not immediately obvious.

PRO TIP: Avoid unnecessarily concise code! Technically speaking, we could make this code even more concise in Lucee CFML (and other languages) by removing the curly-braces of the comparison operator, leaving us with a single-line of code:

values.sort( ( a, b ) => a.len() - b.len() );

This short-hand syntax for fat-arrow functions implicitly returns the result of the subtraction. This is definitely fun for code golf competitions; but, I would recommend that you avoid this level of conciseness in code that you are writing as part of a team.

Historically, I've always through about sort operations in terms of three static outcomes: -1, 0, and 1. However, when we think about sort operations in terms of what they really are: values ranges, we can start to simplify our sort logic. In this case, we can use a single Subtraction expression to sort an array of values by length in Lucee CFML 5.2.9.31.



Reader Comments

@All,

Ha ha, and of course, MDN (Mozilla Developer Network) calls out this approach right in its documentation:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
	return a - b;
});
console.log(numbers);

// [1, 2, 3, 4, 5]

Just another case where "Reading the documentation" would reveal all the gems that you discover as you code. :face-palm:

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.