In ColdFusion how do I tell if I'm at the end of a list. I know about listLast, but this just returns the last data in the list. I want to know when the list has finished. I need to know this so i can change a string accordingly.
ie. mystring = product with list1, list2 and listlast
Its so i know when to add the "and" to my output.
Jason
listLen() will give you the total of items. Then as you loop through the list check if you've reach listLen()
How about using ListLen(list [, delimiters ])? It returns the number of elements in a list.
<cfscript>
var i = 1;
var listLength = ListLen(mystring);
for (i = 1; i lte listLength; i++)
{
product = ListGetAt(mystring, i);
}
</cfscript>
Here's a reference of other List Functions.
#Jason Congerton
use index="i" (or anything) and output in the list using #i#
You could also use
<cfloop from="1" to="listlen(yourlist)#" index="i">
Number #i# #ListGetAt(yourList, i)#<br>
</cfloop>
i gives you the placement in the list and the ListGetAt() function pulls out the value in that place in the list. This will work if your list is 1 or 10,000 names.
CF function list.
http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions-pt0_13.html
specifically, look at ListLast() function.
The last user in the list is: ListLast(temp)
Not sure what you're doing that requires you to know the last element of the list but you can also just use cfloop list =
<cfset myList = "me,myself,i">
<cfoutput>
<cfloop list = "#myList#" index = "i">
#i#
</cfloop>
</cfoutput>
Related
I have a two lists: One that is dynamic, based off of a recordcount of students and the other is the students..i.e., 001,002,003, etc. and 123456 (student data). I need some help being able to randomly assign students one of those numbers. For example, if I have 5 students (123456,234561, etc.), I need to be able to randomly assign 001,002, etc. to those students. So far, hitting a wall. The following is what I have so far:
<cfquery name="testjd" datasource="student">
SELECT SoonerID FROM dbo.CurrentStudentData
WHERE status = 'Student' OR status = 'JD'
</cfquery>
<cfset testList = valueList(testjd.soonerid)>
<cfset totalRecordsJd = #testjd.recordcount#>
<cfloop from="001" to="#totalRecordsJd#" index="i">
<cfset examNo = i>
<cfif len(i) is 1>
<cfset examNo = "00" & i>
<cfelseif len(i) is 2>
<cfset examNo = "0" & i>
</cfif>
<cfif not listFind(usedJDExams, examNo)>
#examNo#<!---is NOT being used!---><br/>
</cfif>
</cfloop>
CF9 makes it a little less fun than later versions. I believe this should work (except for my query mockup).
https://trycf.com/gist/3667b4a650efe702981cb934cd325b08/acf?theme=monokai
First, I create the fake query.
<!---
Simple faked up query data. This is just demo data. I think this
queryNew() syntax was first available in CF10.
--->
<cfscript>
testjd = queryNew("SoonerID", "varchar", [
["123456"],
["564798"],
["147258"],
["369741"]
]);
</cfscript>
Now that I've got a list of students who need tests, I create an array of numbers for those tests.
<!--- Create array of Available Exam Numbers --->
<cfset examNos = ArrayNew(1)>
<cfloop from=1 to=100 index="i">
<cfset examNos[i] = right('00' & i,3)>
</cfloop>
We now combine the two sets of data to get the Exam Numbers assigned to the Student.
<!--- Loop through the query and build random assignments --->
<cfloop query="#testjd#">
<!---Get random exam no.--->
<cfset examNo = examNos[randrange(1,arrayLen(examNos))]>
<!---Build struct of exam assignments--->
<cfset testAssignments[SoonerID] = examNo>
<!---Delete that num from exam array. No reuse.--->
<cfset blah = arrayDelete(examNos,examNo)>
</cfloop>
And this gives us
<cfoutput>
<cfloop collection="#testAssignments#" item="i">
For User #i#, the test is #testAssignments[i]#.<br>
</cfloop>
</cfoutput>
The unused tests are: <cfoutput>#ArrayToList(examNos)#</cfoutput>.
--------------------------------------------------------------------
For User 369741, the test is 054.
For User 147258, the test is 080.
For User 564798, the test is 066.
For User 123456, the test is 005.
The unused tests are:
001,002,003,004,006,007,008,009,010
,011,012,013,014,015,016,017,018,019,020
,021,022,023,024,025,026,027,028,029,030
,031,032,033,034,035,036,037,038,039,040
,041,042,043,044,045,046,047,048,049,050
,051,052,053,055,056,057,058,059,060
,061,062,063,064,065,067,068,069,070
,071,072,073,074,075,076,077,078,079
,081,082,083,084,085,086,087,088,089,090
,091,092,093,094,095,096,097,098,099,100.
A couple of code review notes for the OP code:
1) It's easier to work with arrays or structures than it is to work with a list.
2) cfloop from="001" to="#totalRecordsJd#": from "001" is a string that you are comparing to an integer. ColdFusion will convert "001" to a number in the background, so that it can actually start the loop. Watch out for expected data types, and make sure you use arguments as they were intended to be used.
3) cfif len(i) is 1...: First, it's less processing to build this string in one pass and them trim it - right('00' & i,3). Second (and this is a personal nitpick), is and eq do essentially the same thing, but I've always found it good practice to apply is to string-ish things and eq to number-ish things.
=====================================================================
For CF10+, I would use something like
https://trycf.com/gist/82694ff715fecd328c129b255c809183/acf2016?theme=monokai
<cfscript>
// Create array of random string assignments for ExamNo
examNos = [] ;
for(i=1; i lte 100;i++) {
arrayAppend(examNos,right('00'& i,3)) ;
}
///// Now do the work. ////
//Create a struct to hold final results
testAssignments = {} ;
// Loop through the query and build random assignments
for(row in testjd) {
// Get random exam no.
examNo = examNos[randrange(1,arrayLen(examNos))] ;
// Build struct of exam assignments
structInsert(testAssignments, row.SoonerID, examNo) ;
// Delete that num from exam array. No reuse.
arrayDelete(examNos,examNo) ;
}
</cfscript>
If it is a small query, why not just sort the records (psuedo) randomly using NEWID()? Since the records will already be randomized, you cna use query.currentRow to build the "examNo".
<cfquery name="testjd" datasource="student">
SELECT SoonerID
FROM CurrentStudentData
WHERE status IN ('Student', 'JD')
ORDER BY NewID()
</cfquery>
<cfoutput query="yourQuery">
#yourQuery.SoonerID# #NumberFormat(yourQuery.currentRow, "000000")#<br>
</cfoutput>
This is a formatted comment. After you run this query:
<cfquery name="testjd" datasource="student">
SELECT SoonerID FROM dbo.CurrentStudentData
WHERE status = 'Student' OR status = 'JD'
</cfquery>
Do this:
<cfset QueryAddColumn(testJd, "newcolumn", arrayNew(1))>
Then loop through the queries and assign values to the new column with QuerySetCell.
I need to split one list, delimited by ;, into multiple sub lists. Can I do it without converting it into an array in ColdFusion?
Example: My_list contains:
[10043,10044,10045,10046:2,5,3,1;3453,2167:1,0;2346,8674,9043,7543,6453:0,4,2,0,1]
I need:
My_list1 = [10043,10044,10045,10046:2,5,3,1]
My_list2 = [3453,2167:1,0]
My_list3 = [2346,8674,9043,7543,6453:0,4,2,0,1]
... and so on.
You don't need to "do" anything. A list is just a delimited string. So if you want to set those (very poorly named, IMO) variables, it's just a matter of:
<cfset fullList = "10043,10044,10045,10046:2,5,3,1;3453,2167:1,0;2346,8674,9043,7543,6453:0,4,2,0,1">
<cfset varIndex = 0>
<cfloop index="subList" list="#fullList#" delimiters=";">
<cfset "My_list#++varIndex#" = subList>
</cfloop>
<cfdump var="#variables#">
I seriously wouldn't use dynamic variable names like that though, I'd use an array.
I'm getting cfquery result like below.This result set for one field value(single line).
Memberof =
"CN=AG-880-ExpenseReports,CN=Users,DC=alcco,DC=com, CN=HTTP Users,CN=Users,DC=alcco,DC=com, CN=WA Houses,CN=Users,DC=alcco,DC=com, CN=MTViewMeadows,CN=Users,DC=alcco,DC=com, CN=ALC0169,CN=Users,DC=alcco,DC=com, CN=ALC0069,CN=Users,DC=alcco,DC=com"
have to take as a list of number ALC0169,ALC0069 from this also i need values only 0169,0069.. from that result set.
Is there a way of doing this with coldfusion?
Here is a very straightforward string processing script that will print out the list of numbers you are looking for based on your description. Where I am printing out the numbers, you will want to capture those into an array or other structure depending on what you want to do with the data.
<cfscript>
memberOf = "CN=AG-880-ExpenseReports,CN=Users,DC=alcco,DC=com,CN=HTTP Users,CN=Users,DC=alcco,DC=com,CN=WA Houses,CN=Users,DC=alcco,DC=com,CN=MTViewMeadows,CN=Users,DC=alcco,DC=com,CN=ALC0169,CN=Users,DC=alcco,DC=com,CN=ALC0069,CN=Users,DC=alcco,DC=com";
memberOf = Replace(memberOf, "CN=Users,DC=alcco,DC=com", "", "all");
memberOf = Replace(memberOf, ",,", ",", "all");
memberOf = Replace(memberOf, "CN=", "", "all");
memberArray = ListToArray(memberOf);
</cfscript>
<cfoutput>
#memberOf#<br/><br/>
<cfloop array="#memberArray#" index="i">
<cfif Left(i, 3) eq "ALC">
#Right(i, Len(i)-3)#<br/>
</cfif>
</cfloop>
</cfoutput>
I would use list functions. Note that you can specify your own delimiter. From your other post, your cfldap tag returned a query named GroupSearch.
<cfset ALCNumbers = "">
<cfloop list = "GroupSearch.MemberOf" Index = "MemberOfThis">
<cfif ListFirst(MemberOfThis, delimiters = "=") is "CN"
and left(ListLast MemberOfThis, delimiters = "="), 3) is "ALC">
<cfset ALCNumbers = ListAppend(ALCNUmbers, mid(ListLast MemberOfThis, delimiters = "="), 4,
len(ListLast MemberOfThis, delimiters = "=") - 3)>
</cfif>
</cfloop>
This may have syntax errors because I simply typed it into the textarea. However, it shows the general idea.
Whats the difference between listContains() and listFind() / listFindNoCase()?
They are all list functions, take the same parameters, and return the same result.
listContains looks for the value anywhere in a string, so for example
<cfset list = '1,2,33,4,5' />
<cfdump var="#listContains(list,3)#">
Would return 3 because a 3 is found in the 3rd list item.
listFind looks for the value AS one of the list items.
<cfdump var="#listFind(list,3)#">
Returns 0 because 3 is not one of the list items.
I have a shopping cart array which holds the product information of items in the cart. Some products which are added will be at a special discount if purchased with the "main item".
If someone adds an item which has special offers associated to it, I set a key in the item structure called mainitem with a value of yes, all the subsequent special offer items associated to the main item have key mainitem set as no and have another key named mainitemid (this is the mainitem uid).
If some one deletes the mainitem I need to ensure any related special offer items are deleted as well. This is what I am having trouble with, can't quite work out how to find them all.
I am using form.itemID to supply the item id of the product being deleted. I need to make sure the item being deleted is a main item; if so, loop through the rest of the cart and find any item with mainitemid equal to form.itemid and remove them, then delete the mainitem.
mainitem is defined as session.mycart[i].mainitem
maintitenid is defined as session.mycart[i].mainitemid
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
</cfloop>
Do I need to create two loops or could I do it with one? I'm not sure of my conditional statements.
Solution for OPs specific issue
Revised to offer a more complete solution
<!--- First of all, will need to go 1 to ArrayLen() to find the main item id. Doesn't matter what order you go here, but main items should be inserted before children so may as well start at the bottom --->
<cfloop index="i" from="1" to="#ArrayLen(session.mycart)#">
<!--- Is this the main item? --->
<cfif session.mycart[i].id EQ form.itemID>
<!--- It's found the item, is it a main item? We've found the item so entering here to break the loop --->
<cfif session.mycart[i].mainitem>
<!--- Go from ArrayLen() to 1, as if you go 1 to ArrayLen() when deleting you confuse Coldfusion --->
<cfloop index="j" from="#ArrayLen(session.mycart)#" to="1" step="-1">
<cfif session.mycart[j].id EQ form.itemID OR session.mycart[j].mainitemid EQ form.itemID>
<cfset ArrayDeleteAt(session.mycart,j)>
</cfif>
</cfloop>
</cfif>
<!--- This loop is done, so break out --->
<cfbreak>
</cfif>
</cfloop>
In your post, you state you are looping from index 1 to index ArrayLen(...). If you are deleting items from the array though, Coldfusion is a bit straightforward and doesn't really pay attention and so when you delete element 2 of a 5-element array, the array becomes 4 elements long (because you deleted one) and the element which was index 3 is now index 2, thus it is missed.
The way around this is to start at the end and work backwards. As long as you are deleting at most 1 record at a time, then this is perfectly valid as it will continue reducing the index it is currently checking until you get down to 1, which will be the first element.
This way you can go through element 5, 4, 3, 2, delete element 2, and it will then check index 1 which now will still be the same item as it was when you started the loop and thus no skipping is experienced.
Some blurb on how to deal with this
I misread the question, or it got edited while writing this, but the below is applicable so leaving it here still
Have you considered having special offer items as children to the main item, as then it encapsulates the entire offer together and deleting the parent deletes the child. I've a similar issue where items have options associated with them, and to allow hierarchy to be observed I decided on creating a tree combined with a reference array. Just as a rough example to show the principle, take a look at this
<cfscript>
// Create a main item structure
stcMainItem = {
id = CreateUUID(),
somekey = somevalue,
someotherkey = someothervalue,
cost = 123
};
// Create some child item structures, special offers for ex
stcSpecialOfferItem1 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
stcSpecialOfferItem2 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
// Each is added to a reference array
arrItemReference = [];
arrItemRefernce.add(stcMainItem);
arrItemRefernce.add(stcSpecialOfferItem1);
arrItemRefernce.add(stcSpecialOfferItem2);
// Now you decide to delete the main item and direct parents
strIDToDelete = stcMainItem.id;
for (i=ArrayLen(arrItemReference);i>=1;i--) {
if (
arrItemReference[i].id == strIDToDelete
||
arrItemReference[i].parent == strIDToDelete
) {
ArrayDeleteAt(arrItemReference,i);
}
}
</cfscript>
In my actual code I've done this by way of creating an Item.cfc with methods to deal with the above and to cascade up the tree deleting grandchildren etc, but the principle is sound. Essentially you have methods that allow the items to be exposed as both a flat array and as a hierarchy of parents, children, and siblings.
Informational point
As an aside, you keep interchanging "array" and "structure" when the two are slightly different unlike languages like PHP where array is used to refere to both terms. An array contains values with a numeric index from 1 up to n, while a structure is an object that holds values related to arbitary keys. The reason that there is a difference though is that they don't build off the same base java objects so some methods that work on one don't work on the other.
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
<cfif session.mycart[i].mainitem eq "no" AND session.mycart[i].mainitemid EQ form.itemid>
<cfset arrayDeleteAt(session.myCart,i) />
<cfset i = i - 1 /><!--- this is necessary to keep you from running out of the end of the loop --->
</cfif>
</cfloop>
This isnt tested of course and you may have to work with it to put your exact variable names in, but I think it should get you there. Let me know if I have misunderstood the question.
I don't see a way to do this in one loop, as I view it as a two step process. First you have to see if the item id you deleted is a main item, then go back through the array and delete the rest of the associated items.
Add StructKeyExists() as necessary.
<!--- Find Main Item --->
<cfset isMainItem = false />
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].ItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'Yes'>
<cfset isMainItem = true />
<cfbreak />
</cfif>
</cfloop>
<cfif isMainItem>
<!--- Clean up special offer items --->
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].MainItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'No'>
<cfset ArrayDeleteAt( Sesion.MyCart, Item ) />
<cfset item-- />
</cfif>
</cfloop>
</cfif>