ColdFusion looping through [only part] of a list - list

Really struggling with finding a way to loop through only part (or half maybe?) of a coldfusion list. I've got an if statement set up to check the length of the list and if it is over 30... I want to split the list into the first 30 and the remainder? Not sure if that's the best solution though. I really don't need much detail I'm sure I can figure that much out myself I am more looking just to be pointed in the right direction...

Rather than looping over the list, loop from 1 to a number, and use listGetAt() in the loop. For the remainder of the list, just loop from #myvar + 1# to #listLen#.
<cfoutput>
<cfloop from="1" to="#myVar#" index="idx">
#listGetAt( myList, idx )#<br />
</cfloop>
</cfoutput>
Granted, it's not the most efficient method. If you encounter performance issues, might want to convert the list to an array via listToArray(), and then do:
<cfset myArray = listToArray( myList ) />
<cfoutput>
<cfloop from="1" to="#myVar#" index="idx">
#myArray[ idx ]#<br />
</cfloop>
</cfoutput>

You can make use of the underlying java functions.
<cfscript>
testList = "1,2,3,4,5,6,7,8,9,10,...,43,44";
listAsArray = listToArray(testList);
testChunk = listAsArray.subList(0,30);
</cfscript>
will give you an array "testChunk" with the first 30 items in the list. You can now easily loop over the elements of the Array.
To make this more clear, here is an example:
<cfscript>
testList = "";
maxChunkLength = 30;
for (i=1;i lte 100; i=i+1){
testList = listAppend(testList, i);
}
numOfChunks = ceiling(listLen(testList)/maxChunkLength);
listAsArray = listToArray(testList);
numOfItems = arraylen(listAsArray);
for (k=1;k lte numOfChunks; k=k+1){
startItem = (k - 1) * maxChunkLength;
endItem = startItem + maxChunkLength;
if (endItem gt numOfItems){
endItem = numOfItems;
}
writeOutput(listAsArray.subList(startItem, endItem).toString() & "<br />");
}
</cfscript>

it really depends on what you are trying to accomplish, how the 2 separated sets of data will be used and whst type of data you have in the list... does the data really need to be separated? can you just:
<cfloop from="31" to="#listLen(myList)#" index="i">
#listGetAt(myList, i)#
</cfloop>
If you don't need to take the extra step of separating into 2 lists or arrays, save your self some coding & execution time
Also - if your starting point changes, you can always:
<cfloop from="#start#" to="#listLen(myList)#" index="i">
#listGetAt(myList, i)#
</cfloop>
-sean

In CF10 or Railo 4, you could use the first() and rest() functions from Underscore.cfc to split up your list:
_ = new Underscore();
myArray = listToArray(myList);
firstThirty = _.first(myArray, 30);
remaining = _.rest(myArray, 31);
_.first() and _.rest() both return new arrays based on the index passed into them. The methods simply delegate to the native arraySlice() function, but they can help you write more expressive code.
Note: I wrote Underscore.cfc

Related

ColdFusion: Is it possible to get the index from a cfscript for in loop?

So I'm iterating through an array of structs using a for in loop
for(item in array) {
processStruct(item)
}
Pretty straightforward, What I'm trying to do is get the current index in the for in loop and pass it along as well to the function: processStruct(item, index). I know I can do this with a regular for loop and it's also possible with the tag version <cfloop>
<cfloop array="#myArray#" index="i">
#i#
<cfloop>
The tag variant <cfloop> offers item and index starting with ColdFusion 2016 (or Railo/Lucee).
<cfset x = [ "a", "b", "c" ]>
<cfloop array="#x#" index="idx" item="it">
<cfoutput>#idx#:#it#</cfoutput>
</cfloop>
<!--- returns 1:a 2:b 3:c --->
All ColdFusion versions prior to 2016 do not, so you would have to do it by yourself:
<cfset x = [ "a", "b", "c" ]>
<cfset idx = 1>
<cfloop array="#x#" index="it">
<cfoutput>#idx#:#it#</cfoutput>
<cfset idx++>
</cfloop>
<!--- returns 1:a 2:b 3:c --->
The script variant doesn't support it and most likely never will. Java's Iterator interface doesn't offer it either.
As of CF11 you can use a member function. That way you have access to both the element and the index:
myArray = ["a", "b", "c"];
// By arrayEach() member function CF11+
myArray.each(function(element, index) {
writeOuput(element & " : " & index);
});
No, you don't have an index on the for ... in loop. Just set an index of your own:
var idx = 1;
for( item in struct ){
processStruct( item, idx );
idx++;
}
Try this (where i is your array index):
for (i=1; i lte ArrayLen(yourArray); i++){
processStruct(yourArray[i],i);
}

ArraySum of a list

I am trying to add all the values of all the numbers in a list.
So this is what I tried,
<cfloop query="get_total_merchant">
<cfset tx_amt_total = #tx_amount# + (#tx_amount# * (#merchantFee#/100))>
#ArraySum(tx_amt_total)#
</cfloop>
So basically what tx_amt_total will display is something like 1 2 3 4. So I am trying to add 1 + 2 + 3 + 4 which should give me 10.
However, from what I tried, I am getting an error message: Object of type class java.lang.Double cannot be used as an array
So how do I fix my code?
<cfset tx_amt_total = 0 />
<cfloop query="get_total_merchant">
<cfset tx_amt_total += (tx_amount + (tx_amount * (merchantFee/100))) />
</cfloop>
should be enough. You don not need arraySum()

Breaking a Value List to show 5 records in one Line and then move to Next line and so on

I have the following code where i am tryin to use mod operator to list 5 items on the line and then move to next line, On the single line it should display 5 items and then in next line, it should display the remaining items, if the remaining items are more than 5, it should go to 3rd line then
i am trying this code: but it not doing anything
<cfset items = "1,2,3,4,5,6,7,8,9,0">
<cfif listLen(items) mod 5>
<cfoutput>
#items##Chr(10)##chr(13)#TEST
</cfoutput>
</cfif>
it is displaying all in one line
There are a couple of things wrong with your code.
You are not looping over the list to display each item.
Chr(10) and Chr(13) (linefeed and carriage return) do not display in HTML and your browser.
I modified your code like this:
<cfset counter = 0>
<cfset items = "1,2,3,4,5,6,7,8,9,0,a,b,c">
<cfloop index="thisItem" list="#items#">
<cfset counter = counter + 1>
<cfif counter mod 5>
<cfoutput>#thisItem#, </cfoutput>
<cfelse>
<cfoutput>#thisItem#<br></cfoutput>
</cfif>
</cfloop>
Try it here
and here is an example of that same logic using cfscript syntax:
<cfscript>
counter = 0;
items = "1,2,3,4,5,6,7,8,9,0,a,b,c";
for (counter = 1; counter lte listlen(items); counter++) {
if (counter mod 5) {
writeOutput('#listGetAt(items,counter)#, ');
} else {
writeOutput('#listGetAt(items,counter)#<br>');
}
}
</cfscript>
Try it here
The code I have given you here can be cleaned up a bit but hopefully it is easy to understand for you.
Here is another approach if you are on CF10+:
<cfscript>
// Items List
items_list = "1,2,3,4,5,6,7,8,9,0,a,b,c";
// Convert to array
items_array = items_list.listToArray( "," );
// Item Count
itemCount = arrayLen( items_array );
// Display
for ( i = 1; i <= itemCount; i += 5) {
writeOutput( items_array.slice( i, i + 5 - 1 > itemCount ? itemCount % 5 : 5 ).toList( "," ) & "<br>" );
}
</cfscript>
Here is the TryCF.
Here is another approach.
<cfset items = "1,2,3,4,5,6,7,8,9,0,a,b,c">
<cfoutput>
<cfloop from="1" to="#listLen(items)#" index="i">
#listGetAt(items,i)#
<cfif i mod 5 eq 0>
<br>
<cfelseif i neq listLen(items)>
,
</cfif>
</cfloop>
</cfoutput>
Results in
1 , 2 , 3 , 4 , 5
6 , 7 , 8 , 9 , 0
a , b , c

CFML Efficiently determine if a value exists in one of multiple lists

This is my first post to SO, a resource that is incredibly valuable!
I am trying to determine if a value (state code) exists in a list of codes and if so, set a new variable that represents the division (ie. sales territory, no match = 15). The code below works, but I want to improve my skills and do this as efficiently as possible. So my question is there a "better" way to achieve the same result?
<cfset state = "LA">
<cfset division = 15>
<cfset position = 0>
<cfset aStates = ArrayNew(1)>
<cfset aStates[1] = "MA,ME,NH,RI,VT">
<cfset aStates[2] = "CT,DE,NJ,NY,DE">
<cfset aStates[3] = "DC,MD,VA,WV">
<cfset aStates[4] = "TN">
<cfset aStates[5] = "NC,SC">
<cfset aStates[6] = "GA">
<cfset aStates[7] = "FL">
<cfset aStates[8] = "AL,KY,LA,MS">
<cfset aStates[9] = "IL,WI">
<cfset aStates[10] = "CO,MN,ND,SD,WY">
<cfset aStates[11] = "IN,OH,MI">
<cfset aStates[12] = "ID,OR,UT,WA">
<cfset aStates[13] = "AZ,HI,NV">
<cfset aStates[14] = "CA">
<cfset position = 0>
<cfloop array="#aStates#" index="lStates">
<cfset position = position+1>
<cfif ListFindNoCase(lStates,variables.state) NEQ 0>
<cfset division = position>
</cfif>
</cfloop>
<cfdump var="#aStates#" label="states array">
<cfoutput>State: #state#</cfoutput>
<cfoutput>Division: #division#</cfoutput>
With the current structure, you are relying on string comparisons, so there is not too much room for improvement.
Unless there is a specific reason you must hard code the values, a more flexible approach is to store the information the database. Create tables to store the "states" and "divisions", and another table to store the relationships:
States
StateID | Code | Name
1 | ME | Maine
2 | GA | Georgia
3 | CA | California
4 | NH | New Hampshire
...
Divisions
DivisionID | Name
1 | Sales Territory
...
DivisionStates
DivisionID | StateID
1 | 1
1 | 4
6 | 2
14 | 3
...
Then use an OUTER JOIN to retrieve the selected state and division. Assuming #state# will always contain a valid entry, something along these lines (not tested):
SELECT s.Name AS StateName
, CASE WHEN d.Name IS NULL THEN 'No Match' ELSE d.Name END AS DivisionName
FROM States s
LEFT JOIN DivisionStates ds ON ds.StateID = s.StateID
LEFT JOIN Divisions d ON d.DivisionID = ds.DivisionID
WHERE s.Code = <cfqueryparam value="#state#" cfsqltype="cf_sql_varchar">
I do not know the source of the #state# variable, but I am guessing it may be passed via a form <select>. If it is currently hard coded, you could simplify things further by using querying the "states" table, and using it to populate the list. Also, consider using the ID as the list value, rather than the state "code".
I thought of an approach in this by placing the states into a Struct; with the state codes as keys.
<cfscript>
state = "LA";
division = 15;
states_divisions_map = {
MA: 1, ME: 1, NH: 1, RI: 1, VT: 1,
CT: 2, NJ: 2, NY: 2, DE: 2,
DC: 3, MD: 3, VA: 3, WV: 3,
TN: 4,
NC: 5, SC: 5,
GA: 6,
FL: 7,
AL: 8, KY: 8, LA: 8, MS: 8,
IL: 9, WI: 9,
CO: 10, MN: 10, ND: 10, SD: 10, WY: 10,
IN: 11, OH: 11, MI: 11,
ID: 12, OR: 12, UT: 12, WA: 12,
AZ: 13, HI: 13, NV: 13,
CA: 14
};
if(StructKeyExists (states_divisions_map, state) ){
division = states_divisions_map[state];
}
writeDump(var: states_divisions_map, label: "States");
writeOutput("State: #state#");
writeOutput("Division: #division#");
</cfscript>
This way you can quickly check for the state without all the loops.
Observation: In your original code, DE is present twice.
ListContains() is like ListFind(), but it searches for a list element that contains the the text you're looking for anywhere in it. (ListFind() looks for a list element that entirely matches the string. Generally ListFind() is the better tool but not in this case.
(CF does have an ArrayContains, but it doesn't work for this task, it merely tells you if the array contains an exactly matched element.)
Because you're searching for unique two-char state-codes, this is a perfect use of the function.
<cfset aStates = ArrayNew(1)>
<cfset aStates[1] = "MA,ME,NH,RI,VT">
<cfset aStates[2] = "CT,DE,NJ,NY,DE">
<cfset aStates[3] = "DC,MD,VA,WV">
<cfset aStates[4] = "TN">
<cfset aStates[5] = "NC,SC">
<cfset aStates[6] = "GA">
<cfset aStates[7] = "FL">
<cfset aStates[8] = "AL,KY,LA,MS">
<cfset aStates[9] = "IL,WI">
<cfset aStates[10] = "CO,MN,ND,SD,WY">
<cfset aStates[11] = "IN,OH,MI">
<cfset aStates[12] = "ID,OR,UT,WA">
<cfset aStates[13] = "AZ,HI,NV">
<cfset aStates[14] = "CA">
<cfset lstates = arraytolist(astates,"|")>
<cfset division = listcontainsnocase(lstates,"CO","|")>
<cfoutput>Division: #division#<br>
State: #state#</cfoutput>
We set the delimiter to | (pipe), but you can use anything, other than the default (comma), because you're using that for you're sub-delimiter.
For future reference, if you're using CF 9 (I believe) or after, you can use array shorthand for quickly building arrays.
<cfset aStates=["MA,ME,NH,RI,VT","CT,DE,NJ,NV,DE","DC,MD,VA,WV"]> ...
While it may seem merely like a stylistic difference, it saves a lot of time.
Structures can be created similarly.
<cfset MyDogIs = {Size = "medium", Color = "Black", HasPaws = ["FL","FR","BL","BR"]}>
(And you can nest implicit array and structs within another!)
Thank you all for your input. I am answering this question myself with the code I decided to use since it is based on piece supplied by multiple answers/comments. I hope this is the correct way to do this, if not please advise.
The divisions were not stored in the db as they are a set it and forget it mapping that has not changed in years and I thought I'd save a call to the DB. If they were subject to change I would have taken a table approach suggested by #leigh
Thanks to #matt-busche for the improved loop, ucase() and break recommendations, and #cfqueryparam for the short hand array suggestion.
Here's what I went with. The code is actually a function in a cfc that processes a form submission but I have pasted only the relevant part.
<cfset form.division = 15>
<cfset aDivisions = ["MA,ME,NH,RI,VT",
"CT,DE,NJ,NY,DE",
"DC,MD,VA,WV",
"TN",
"NC,SC",
"GA",
"FL",
"AL,KY,LA,MS",
"IL,WI",
"CO,MN,ND,SD,WY",
"IN,OH,MI",
"ID,OR,UT,WA",
"AZ,HI,NV",
"CA"]>
<cfloop from="1" to="#ArrayLen(aDivisions)#" index="i">
<cfif ListFind(aDivisions[i],Ucase(arguments.sForm.state)) NEQ 0>
<cfset form.division = i>
<cfbreak>
</cfif>
</cfloop>

How to (selectively) convert array elements to a list of values

Given the array below, how do I convert it to a simple list of values?
<cfdump var="#arguments.ServiceTextArray#">
Array Elements:
1 3567_no
2 3584_yes
3 3642_yes
4 3643_yes
5 3644_no
6 3645_no
7 3646_no
Specifically, how do I extract values with the "yes" suffix and produce a list like this?
3584,3642,3643
Thanks in advance.
Would this help? There will always be better solution than this.
<cfset myList = "">
<cfloop from="1" to="#Arraylen(myArray)#" index="index">
<Cfif right(myArray[index],3) EQ "yes">
<cfset myList = listAppend(myList, listFirst(myArray[index], '_'))>
</Cfif>
</cfloop>
<cfoutput>#myList#</cfoutput>
This is partly an extension to Henry answer but should be exactly what your after:
<cfscript>
tmpArray = ['567_no','584_yes','3642_yes','3643_yes','3644_no','3645_no','3646_no'];
list = "";
for (item in tmpArray)
if (listLast(item, "_") == "yes")
list = listAppend(list, listFirst(item, "_"));
writeDump(list);
var list = "";
for (item in array)
if (ListLast(item, "_"))
list = listAppend(list, val(item));
Using the Underscore.cfc library (CF 10 only):
filteredArray = _.filter(arguments.ServiceTextArray, function(val) {
return (val contains 'yes');
});
resultArray = _.map(filteredArray, function(val) {
return left(val, 4);
});
list = arrayToList(resultArray);
(I created this library, BTW)