Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: James Brooks
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: James Brooks

Ask Ben: Sorting Quasi-Numeric Values Like 4K And 3M In ColdFusion

By
Published in ,

Out of the box, ColdFusion provides a .sort() method on arrays that makes it trivial to sort uniform collections; that is, collections which contain uniformly numeric or uniformly text values. But, when you have mixed collections, complex objects, or when you want to implement a "natural sort", the text and numeric sorting strategies fall-short. In such cases, the .sort() method also accepts a callback that can act as the comparison operator. We can use this operator to reduce the elements down to a set of sortable values.

Question: I have an inventory system I built for sorting various electronic parts. For those with values (like resistors) I have them entered like 1, 1K, 1M. Is there a way to sort with ColdFusion that would let me specify that 1K should come after 999 for example, and 1M is higher?

In this case, we need to sort across both numeric values and text values that have a numeric meaning. To do this, we can "expand" the text values into their natural numeric representation within the callback; and then use those values to power the internal sort.

Essentially, I'm going to take any value that ends in K and multiply it by 1,000; and, any value that ends in M and multiply it by 1,000,000. Any value that ends in any other character will be treated as a number:

<cfscript>

	values = [
		100,
		"3M",
		"4.5K",
		"44K",
		"5K",
		200,
		"2.333M",
		"1M",
		999,
		"8.5K"
	];

	// We can't natively sort a mixture of strict numeric values and text abbreviations.
	// But, we can provide a sort operator that normalizes the values and compares them
	// internally. This allows us to perform an in-place sort across a mixed collection.
	values.sort(
		( a, b ) => {

			var aNormalized = normalizeValue( a );
			var bNormalized = normalizeValue( b );

			// ASC sort, reverse math for DESC sort.
			return sgn( aNormalized - bNormalized );

		}
	);

	writeDump(
		label = "Sorted Values",
		var = values
	);

	// Let's output the internal representation that we were using.
	writeDump(
		label = "Internal Representation",
		var = values.map( normalizeValue )
	);

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I normalize the given value for use in the mixed-collection sort.
	*/
	public numeric function normalizeValue( input ) {

		// Note: the val() function will only consume characters up to the first non-
		// numeric digits. As such, it will naturally omit "M" and "K" from parsed value.
		switch ( right( input, 1 ) ) {
			case "M":
				return ( val( input ) * 1000000 );
			break;
			case "K":
				return ( val( input ) * 1000 );
			break;
			default:
				return val( input );
			break;
		}

	}

</cfscript>

Inside the normalizeValue() method, I'm using the val() function to run the maths. The val() function works by consuming the given string input up until it hits the first non-numeric value. At which point, it quietly stops parsing the string input and returns the numeric result.

So, if we execute val( "123Z" ), ColdFusion will return 123, omitting the Z and any characters that come after it. Which means that an equation like:

val( "12K" ) * 1000

... is the same as:

12 * 1000

... which gives us the desired 12,000 numeric equivalent.

All of this is happening internally to the .sort() callback; so, we're not actually changing the underlying array - we're just translating the elements, on the fly, into comparable values. And, when we run this ColdFusion code, we get the following output:

Two CFDump outputs, one of the sorted array and one of the array of normalized values used internally to the sort.

The dump on the left is the original array after the in-place .sort() is applied. Notice that all of the K values are sorted before the M values. This is because we normalized all the values into their numeric equivalents (which are shown in the dump on the right) internally to the .sort() callback.

At a small scale, this should be blazing fast. But, it's worth pointing out that the values a being parsed over and over again during the sorting algorithm—every time the callback is called. If this sorting was being performed in a "hot path" in the application, it might be worth normalizing the values first, then sorting. But, that approach makes an in-place sort more challenging.

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

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

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