January 21, 2019
Union and diff of arrays
Comments
(11)
January 21, 2019
Union and diff of arrays
I try to bend the internet to my will.
Newbie 34 posts
Followers: 24 people
(11)

When working with arrays in CFML there are times when you want to know the values that set A contains, that are not in set B. There isn’t a native function to do this in CFML (you can do looping etc to do it), as CFML is built on Java, we can take advantage of the underlying removeAll() Java method.

For example:

function difference(required array a, required array b) {
    a.removeAll(b);
    return a;
}

set1 = [0,2,4,6];
set2 = [0,3,6,9];

// A - B
result = difference(set1, set2);
writeDump(result); // [2,4]
// if we have this.passArrayByReference=true in ACF or using Lucee
writeDump(set1); // [2,4]

The result from the function is just what we want. We do have a potentially unwanted side effect though, set1 has been mutated inside the function. By default ColdFusion passes arrays by value, but as of 2016 you can globally enable arrays to be passed by reference. Lucee passes arrays by reference by default.

To solve this problem, there is another Java method we can use to copy the array so that it doesn’t get accidentally mutated.

function difference(required array a, required array b) {
    var result = a.clone();
    result.removeAll(b);
    return result;
}

set1 = [0,2,4,6];
set2 = [0,3,6,9];

// A - B
result = difference(set1, set2);
writeDump(result); // [2,4]
// if we have this.passArrayByReference=true in ACF or using Lucee
writeDump(set1); // [0,2,4,6]

Now we have a pure function with no nasty side effects to haunt us.

Union of two Arrays

What if want to find the union (the combined unique values) of the two arrays? There is arrayAppend(a, b, true) which will merge the two arrays, however you can end up with duplicates. For example:

function unionall(required array a, required array b) {
    a.append(b, true);
    return a;
}

set1 = [0,2,4,6];
set2 = [0,3,6];

result = unionall(set1, set2);
writeDump(result); // [0,2,4,6,0,3,6]

// if we have this.passArrayByReference=true in ACF or using Lucee
writeDump(set1); // [0,2,4,6,0,3,6]

As you can see, we have two 0 and two 6 in the resulting array. Another thing to note is that ArrayAppend will mutate the original array – this may be an unwanted side-effect.

CFML does not have a native method to get the unique values of the merged arrays. To only get the unique values in the union, we can once again take advantage of the underlying Java methods.

function union(a, b) {
    var result = a.clone();
    result.removeAll(b);
    result.addAll(b);
    return result;
}

set1 = [0,2,4,6];
set2 = [0,3,6];

// A + B
result = union(set1, set2);
writeDump(result); // [2,4,0,3,6]
writeDump(set1); // [0,2,4,6]

In the example above, you can see that we do not mutate set1 – we solved that using the Java .clone() method. .removeAll() comes in handy to get rid of the duplicates. Then I use .addAll() to append the second array. The result is that we end up with distinct values in the result returned from the function and set1 is unaffected.

Whilst these methods are handy, you can do this all in CFML if you want, but sometimes it’s useful to drop down to the underlying Java. If you have large arrays then you may find that you’ll be better off using Java’s apache commons libraries and covert to non native CFML datatypes.

11 Comments
2019-10-10 13:15:28
2019-10-10 13:15:28

Thanks Tushar.

I did do a couple of related posts which people may be interested to read and it’s quite hard to find related posts on this community portal so here they are:

Intersection of Arrays [https://coldfusion.adobe.com/2019/01/intersection-of-arrays/]

Finding The Symmetric Difference Between Two Arrays [https://coldfusion.adobe.com/2019/01/finding-symmetric-difference-two-arrays/]

 

Like
2019-10-09 09:08:20
2019-10-09 09:08:20

In addition to the above function, there is a chance that you want to retain common elements from both sets.

function retainCommonElements(a, b) {
var result = a.clone();
result.retainAll(b);
return result;
}

set1 = [0,2,4,6];
set2 = [0,3,6];

result = retainCommonElements(set1, set2);
writeDump(result); // 0, 6
writeDump(set1); // 0, 2, 4, 6

Like
2019-02-11 19:17:23
2019-02-11 19:17:23

Whatever you do, put these calls to the underlying java methods in wrapper so that you never call it but one spot in your code base so that whenever Adobe breaks it in a future version cause it’s “undocumented” you won’t have to fight with your codebase for 6 months.

Like
(1)
(1)
>
justint52505276
's comment
2019-02-11 20:58:55
2019-02-11 20:58:55
>
justint52505276
's comment

@Justin Thanks for the comment. In the past I would have agreed with you, these days I think taking advantage of the Java methods is a reasonable thing.

These methods are undocumented in CFML land, but they are documented in Java land. So I take your point that Adobe may change the Java datatype they use for say a ColdFusion array but it is highly unlikely. Lucee and ColdFusion use a slightly different inheritance tree, but these methods still work on both engines. It is of course possible (although unlikely) that Adobe changes the underlying datatype.  However you should have unit tests, so you will know as soon as you update ColdFusion that there is an issue and the change will be the same throughout your code base. If you don’t have code coverage then any change to ColdFusion or the JVM version is a bit of a risk.

Like
(1)
2019-01-23 17:51:17
2019-01-23 17:51:17

How does the Java clone() function compare to ColdFusion’s duplicate() function?

Like
(2)
>
Carl Von Stetten
's comment
2019-01-24 09:26:42
2019-01-24 09:26:42
>
Carl Von Stetten
's comment

To be honest – I don’t know. I’d imagine that duplicate is using clone under the hood. If you use either you get back the same java datatype.

Like
>
aliaspooryorik
's comment
2022-01-05 23:33:25
2022-01-05 23:33:25
>
aliaspooryorik
's comment

The ugly question here is whether clone() does a deep copy or a shallow copy.  Core Java objects interpret clone() as a shallow copy, but some higher-level implementations do deep copies.

Arguably the ‘deep copy’ implementations are wrong, since the clone() method doesn’t provide for a context argument so an invocation doesn’t have the context needed to avoid circular references and to avoid making extra copies of embedded members which are referenced more than once.

Of course, that ignores the other elephant in the room, which is the problem of how the underlying methods such as removeAll() compare complex members, which boils down to the implementations of these member objects – which, in turn, will likely lead to unexpected dependencies.

Bottom line is that it’s best to limit use of these methods to arrays of strings, also taking care to ensure that numeric members are formatted consistently so you don’t trip over “1” not being equal to “1.0”

Like
2019-01-22 16:57:38
2019-01-22 16:57:38

Let me make sure I understand.

Arrays are getting passed into the function. Array have member functions above and beyond the ones that Adobe lists. Are there other Java member functions that may be useful? How would I find them?

Like
(3)
>
James Mohler
's comment
2019-01-23 14:42:56
2019-01-23 14:42:56
>
James Mohler
's comment

Regarding finding available Java methods:

In order to find this, we need to get the Java class that is being used with the given data type/object we’re working with. From there it’s just a matter of calling that class’ getMethods() function and iterating over the results; which is an array of Java Method classes.

Here’s an example:

exampleArray = [];
methods = exampleArray.getClass().getMethods();

// If you want to see the raw result
// writeDump(methods);

arrayEach(methods, function(method) {
writeOutput(“#method.getName()#”);
});

 

Like
(2)
>
James Mohler
's comment
2019-01-24 09:20:22
2019-01-24 09:20:22
>
James Mohler
's comment

Sorry about the formatting – I don’t seem to be able to fix it!Arrays are based on java.util.AbstractList. You can find the super class by doing:—exampleArray = [];writeDump(exampleArray.getClass().getSuperClass().getSuperClass().getName());writeDump(exampleArray.getClass().getSuperClass().getSuperClass().getSuperClass().getName());—In ACF (2018) that’ll give you:—java.util.ArrayListjava.util.AbstractList—In Lucee (5) that’ll give you:—lucee.runtime.type.util.ArraySupportjava.util.AbstractList—You can then look up the methods of java.util.AbstractList

Like
(1)
>
aliaspooryorik
's comment
2019-01-24 14:57:02
2019-01-24 14:57:02
>
aliaspooryorik
's comment

Here is a link to the JavaDocs for AbstractList: https://docs.oracle.com/javase/8/docs/api/java/util/AbstractList.html

 

Like
(2)
Add Comment