Last active
March 11, 2025 19:57
-
-
Save JamoCA/103625ffdf9688b810485183db9b648f to your computer and use it in GitHub Desktop.
listGetDuplicates2016: Returns duplicate items in a list. Mimics the behavior of ListGetDuplicates from ColdFusion 2025... and adds a couple more options. Compatible with ColdFusion 2016.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<cfscript> | |
/** | |
* listGetDuplicates2016: Returns duplicate items in a list. | |
* Mimics the behavior of ListGetDuplicates from ColdFusion 2025... and adds a couple more options. Compatible with ColdFusion 2016. | |
* @displayname listGetDuplicates2016 | |
* @author James Moberg http://sunstarmedia.com, @sunstarmedia | |
* @version 1 | |
* @lastUpdate 3/11/2025 | |
* @gist https://gist.github.com/JamoCA/103625ffdf9688b810485183db9b648f | |
* @blog https://dev.to/gamesover/backporting-new-coldfusion-2025-function-listgetduplicates-i9n | |
* @twitter https://x.com/gamesover/status/1899546035552411904 | |
* @LinkedIn https://www.linkedin.com/posts/jamesmoberg_coldfusion-cfml-adobecoldfusion-activity-7305313174294081536-sSUd | |
* @param list The input list (or array) to process | |
* @param delimiter Character(s) separating list elements (default: ",") | |
* @param ignoreCase Boolean to determine case-sensitive comparison (default: false) | |
* @param includeEmptyFields Boolean to include empty fields in the result (default: false) | |
* @param trimValues Boolean to ensure values are properly trimmed to avoid false duplicates. (default: true) | |
* @param returnFirstMatch Boolean to return the first match and retain original sort order (default: false) | |
* @param returnDelimiter Character(s) returned list elements (default: the original "delimiter") | |
* @param returnAsArray Return the list elements as an array (default: false) | |
* @return A new list containing duplicate items | |
*/ | |
string function listGetDuplicates2016( | |
required any list, | |
string delimiter = ",", | |
boolean ignoreCase = false, | |
boolean includeEmptyFields = false, | |
boolean trimValues = true, | |
boolean returnFirstMatch = false, | |
string returnDelimiter = "", | |
boolean returnAsArray = false | |
) { | |
// Convert list to array for easier processing | |
local.listArray = isarray(arguments.list) ? arguments.list : (issimplevalue(arguments.list)) ? listToArray(arguments.list, arguments.delimiter, arguments.includeEmptyFields) : []; | |
local.returnDelimiter = len(arguments.returnDelimiter) ? arguments.returnDelimiter : arguments.delimiter; | |
local.seen = [:]; | |
local.seenHashed = [:]; | |
local.duplicates = [:]; | |
if (arraylen(local.listArray) eq 1) return ""; | |
// Iterate through the array to identify duplicates | |
for (local.i = 1; local.i lte arrayLen(local.listArray); local.i++) { | |
local.currentItem = (arguments.trimValues) ? trim(local.listArray[local.i]) : local.listArray[local.i]; | |
local.key = local.currentItem; | |
// Skip empty fields if includeEmptyFields is false | |
if (!arguments.includeEmptyFields && !len(trim(local.currentItem))) { | |
continue; | |
} | |
// Track items we've seen and mark duplicates | |
if ((arguments.ignoreCase && structKeyExists(local.seen, local.key)) | |
|| (!arguments.ignoreCase && structKeyExists(local.seenHashed, local.key.hashcode())) | |
) { | |
// If already seen, mark as duplicate if not already in duplicates | |
if (!structKeyExists(local.duplicates, local.key)) { | |
local.duplicates[local.key] = local.currentItem; | |
} | |
} else { | |
// First occurrence of this item | |
local.seen[local.key] = local.currentItem; | |
local.hashedKey = (arguments.ignoreCase) ? ucase(local.currentItem).hashcode() : local.currentItem.hashcode(); | |
local.seenHashed[local.hashedKey] = local.currentItem; | |
} | |
} | |
// Build the result array from duplicates | |
local.resultArray = []; | |
if (arguments.returnFirstMatch){ | |
for (local.key in local.seen) { | |
if (arguments.ignoreCase && structKeyExists(local.duplicates, local.key)) { | |
arrayAppend(local.resultArray, local.key); | |
} else if (!arguments.ignoreCase && structKeyExists(local.duplicates, local.key)){ | |
arrayAppend(local.resultArray, local.key); | |
} | |
} | |
} else { | |
for (local.value in local.duplicates) { | |
arrayAppend(local.resultArray, local.duplicates[local.value]); | |
} | |
} | |
// Convert back to an array or list using the specified delimiter | |
return (arguments.returnAsArray) ? local.resultArray : arrayToList(local.resultArray, local.returnDelimiter); | |
} | |
</cfscript> | |
<cfset tests = [ | |
"ACF2025": ["list": "one,one,ONE,two,three,,four,THREE,FOUR,FIVE,", "ignoreCase":true] | |
,"ACF2025: ignoreCase=FALSE": ["list": "one,one,ONE,two,three,,four,THREE,FOUR,FIVE,", "ignoreCase":false] | |
,"ACF2025: numeric list": ["list": "1, 2, 22, 3, 2, 5, 5, 1"] | |
,"ACF2025: dashed list": ["list": "1-2-22-3-2-5-5-1", "delimiter": "-"] | |
,"ACF2025: includeemptyfields=TRUE": ["list": "one,one,ONE,two,three,,,four,THREE,FOUR,FIVE,", "ignorecase": true,"includeemptyfields": true] | |
,"ACF2016: returnFirstMatch=TRUE, ignoreCase=TRUE": ["list": "one,one,ONE,two,three,,four,THREE,FOUR,FIVE,", "ignoreCase":true, "returnFirstMatch": true] | |
,"ACF2016: returnFirstMatch=TRUE": ["list": "one,one,ONE,two,three,,four,THREE,FOUR,FIVE,", "returnFirstMatch": true] | |
]> | |
<cfset rowNum = 0> | |
<cfoutput> | |
<cfloop collection="#tests#" item="test"> | |
<cfset rowNum += 1> | |
<h2>[#rowNum#/#structcount(tests)#] #test#</h2> | |
<cfdump var="#tests[test]#" label="#test#"> | |
<cfset result = listGetDuplicates2016(argumentcollection=tests[test])> | |
<pre>"#result#"</pre> | |
<hr> | |
</cfloop> | |
</cfoutput> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment