Select Page

# Flattening An Array In Lucee CFML

Cyberdime
Published: January 7, 2023

Yesterday, at InVision, I was writing an algorithm in which I needed to build several one-dimensional arrays. And, in some cases, I was using all simple values; but, in other cases, I was using a mixture of simple values and other arrays. To keep my calling code clean, I abstracted the logic into a `flattenArray()` method that would take N-arguments and then smoosh all of those arguments down into a single array. The method I created worked fine, but it just didn’t look “right”. I wasn’t vibing it. As such, I wanted to step back and try creating a flatten method with a variety of different syntaxes to see which strikes the right balance between simplicity, elegance, and readability (which is all highly subjective).

In my case, I only needed the method to flatten one level deep – I wasn’t going to be using any deeply-nested arrays. As such, at least my logic didn’t require any recursion; so, that’s already a win from the get-go. Flattening an array in this manner turns:

`[a, [b, c], d]`

… into:

`[a, b, c, d]`

Note that the `[b,c]` array was “unwrapped” and merged into the final result.

Here are four different approaches that I can think of to flatten an array in ColdFusion (without recursion). I am using `.reduce()`, `.each()`, and two different types of loops:

``````<cfscript>
a = [ "hello", "world" ];
b = "simple";
c = [ "cool", "beans" ];
// NOTE: All of these methods only flatten ONE LEVEL down.
dump( arrayToList( flatten( a, b, c ) ) );
dump( arrayToList( flatten2( a, b, c ) ) );
dump( arrayToList( flatten3( a, b, c ) ) );
dump( arrayToList( flatten4( a, b, c ) ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// APPROACH ONE: Using the .reduce() method.
public array function flatten() {
var results = arguments.reduce(
( reduction, key, value ) => {
return( reduction.append( value, isArray( value ) ) );
},
[]
);
return( results );
}

// APPROACH TWO: Using CFLoop for array values.
public array function flatten2() {
var results = [];
loop
item = "local.value"
array = arguments
{
results.append( value, isArray( value ) );
}
return( results );
}

// APPROACH THREE: Using a for-in loop.
public array function flatten3() {
var results = [];
for ( var key in arguments ) {
results.append( arguments[ key ], isArray( arguments[ key ] ) );
}
return( results );
}

// APPROACH FOUR: Using an each iterator.
public array function flatten4() {
var results = [];
arguments.each(
( key, value ) => {
results.append( value, isArray( value ) );
}
);
return( results );
}
</cfscript>
``````

When we run this code, all four flatten methods yield the same output:

So, these flatten methods all “work”, but which one is the “best”?

As always, one of my first instincts is to use the `.reduce()` method. There is something so alluring about `.reduce()` – it has an air of sophistication and an elegance underscored by classical computer science. I actually feel smarter when I write a `.reduce()` method.

That said, just about every time I’m done writing a `.reduce()` method, I step back and just feel so meh about the whole thing. `.reduce()` always feels way too wordy with lots of values and syntactic noise. As such, 9-in-10 times, I scrap the `.reduce()` approach and use a simplified loop.

At work, I ended up going with approach two: using the `CFLoop` tag to iterate over the arguments collection. One thing that I love about the `CFLoop` tag is that it can expose a number of optional attributes that can surface different aspects of the iteration. Meaning, when iterating over a Struct, I can use both the `key` and `value` attributes; or, just one of them. Similarly, with an Array, I can use both the `item` and `index` attributes; or, just one of them. In other words, the `CFLoop` tag allows me to define only the parts of the loop that I actually need to consume. In my case, I’m exposing the `item` aspect of Array iteration without the `index` since I don’t actually need the `index`.

ASIDE: The `arguments` scope is neither an Array nor a Struct – it’s a specialized scope that has both Array and Struct behaviors, which makes it some kind of wonderful. Calling `isArray(arguments)` and `isStruct(arguments)` both yield `true`.

The `CFLoop` tag approach also feels like it does the most work with the least amount of syntax.

If, instead of creating a variadic method (a method that receives a dynamic number of arguments), I created a method that received a single argument which was an array, then I would probably go with the `for-in` style loop:

``````<cfscript>
public array function flatten( required array values ) {
var results = [];
for ( var value in values ) {
results.append( value, isArray( value ) );
}
return( results );
}
</cfscript>
``````

In my case, since I am using a variadic method, the `for-in` approach uses Struct iteration (of the `arguments` scope), not Array iteration. Which means I have to perform a key-based look-up of the iteration value.

I know this stuff is highly subjective. When I look at the different techniques, I just have to listen to my gut and go with the method that feels like it strikes the right balance of qualities.