Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Steven Guitar
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Steven Guitar ( @stguitar )

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

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel