Skip to main content
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Dan Wilson
Ben Nadel at CFinNC 2009 (Raleigh, North Carolina) with: Dan Wilson ( @DanWilson )

My ColdFusion Weekly Podcast CFQuiz Answer

By on
Tags:

Unfortunately, I did not win in the ColdFusion weekly podcast (congratulations Dan Vega), but I think I had a cool answer that ya'll might be interested in seeing. Here is their question (as taken from the ColdFusion weekly website):

Programming exercise! In CF (obviously) write a script that loops from 1 to 100 and outputs the numbers. When a number is a multiple of 3, output "ColdFusion" and a line break. When a number is a multiple of 5, output "Rocks" and a line break. When a number is a multiple of 3 and 5, output "ColdFusion Rocks" and a line break. Slickest solution wins!

I took a two pronged approach to this answer. The first is a simple index loop using CFLoop (just to satisfy the question). The second approach is a sweet-ass ColdFusion custom tag that actually utilizes the CFExit method="LOOP" attribute (heck yeah!).

Ok, so here's the simple answer:

<!--- Loop from 1 to 100. --->
<cfloop
	index="intI"
	from="1"
	to="100"
	step="1">

	<!--- Output the line number. --->
	#intI#.

	<!--- Check to see if this is a multiple of 3. --->
	<cfif NOT (intI MOD 3)>
		ColdFusion
	</cfif>

	<!--- Check to see if this is a multilple of 5. --->
	<cfif NOT (intI MOD 5)>
		Rocks!
	</cfif>

	<!---
		ASSERT: The above to CFIF statements will handle
		cases were the number is BOTH a multiple of
		3 and of 5. No need to handle those seperately
		(ie. MOD 15 is already being handled).
	--->
	<br />

</cfloop>

Ok, that's pretty basic. But, it's not that flexible. I then created a ColdFusion custom tag to make this more open ended and seriously upped the "cool" factor. Before we get into how it's done, let's look at how it is called:

!--- This can be run as a self-closing tag. --->
<cfmodule
	template="cfweeklyloop.cfm"
	from="1"
	to="100"
	step="1"
	mod3="ColdFusion"
	mod5="Rocks!"
	/>


<!---
	Or it can be run as a tag that allows custom
	output inbetween each line item.
--->
<cfmodule
	template="cfweeklyloop.cfm"
	from="1"
	to="100"
	step="1"
	mod3="ColdFusion"
	mod5="Rocks!">

	<hr />

</cfmodule>

As you can see above, the custom tag (executed via CFModule) uses the attributes "mod3" and "mod5" to accomplish what was accomplished in the first CFLoop tag. Ok, so now, let's look at the code:

<!--- Kill extra output. --->
<cfsilent>

	<!---
		Check to see which version of the tag we are executing.
		We only care about validating the tag attributes on the
		start tag, not on the end tag.
	--->
	<cfswitch expression="#THISTAG.ExecutionMode#">

		<cfcase value="START">


			<!--- Param tag attributes. --->
			<cfparam
				name="ATTRIBUTES.From"
				type="numeric"
				/>

			<cfparam
				name="ATTRIBUTES.To"
				type="numeric"
				/>

			<cfparam
				name="ATTRIBUTES.Step"
				type="numeric"
				default="1"
				/>


			<!---
				Validate the step value to make sure an
				infinite loop will never take place.
			--->
			<cfif (ATTRIBUTES.Step EQ 0)>

				<!--- This tag loop will never end. --->
				<cfthrow
					type="EXCEPTION"
					message="Invalid Loop Parameters"
					detail="The FROM value #ATTRIBUTES.From#, TO value #ATTRIBUTES.To#, and STEP value #ATTRIBUTES.Step# will result in an infinite loop."
					/>

			</cfif>


			<!--- Create a struct of mod value to check. --->
			<cfset THISTAG.ModValues = StructNew() />

			<!---
				Loop over the tag attributes to find the
				mod values. Remember, at the time of the tag
				execution, we are not sure yet which MOD
				attribute are actually being passed in. We
				have to find them manually.
			--->
			<cfloop
				item="strKey"
				collection="#ATTRIBUTES#">

				<!--- Check to see if this key matches the MOD patterns. --->
				<cfif strKey.Matches(
					JavaCast( "string", "(?i)MOD[\d]+" )
					)>

					<!---
						This is a MOD value. We have to store
						both the mod value and the resultant
						output value into the struct.
					--->
					<cfset THISTAG.ModValues[ strKey.ReplaceFirst( "(?i)^MOD", "" ) ] = ATTRIBUTES[ strKey ] />

				</cfif>

			</cfloop>


			<!---
				Now that we have a structure that contains keys
				and output values, we have to get the array of
				keys. We want this key to be in smallest-value
				first as these will be most likely to be output.
			--->
			<cfset THISTAG.ModKeys = StructKeyArray( THISTAG.ModValues ) />

			<!--- Sort the keys. --->
			<cfset ArraySort( THISTAG.ModKeys, "numeric", "ASC" ) />


			<!---
				Initialize the tag to have the index equal to
				the FROM attribute. This is the index that will
				keep track of our loop from iteration to
				iteration.
			--->
			<cfset THISTAG.Index = ATTRIBUTES.From />

		</cfcase>


		<cfcase value="END">

			<!---
				If this is the first iteration, check to see
				if we should even execute it.
			--->
			<cfif (
					(THISTAG.Index EQ ATTRIBUTES.From)
				AND
					(
							(
								(ATTRIBUTES.Step GT 0) AND
								(THISTAG.Index GT ATTRIBUTES.To)
							)
						OR
							(
								(ATTRIBUTES.Step LT 0) AND
								(THISTAG.Index LT ATTRIBUTES.To)
							)
					)
				)>

				<!---
					This tag is starting out of bounds.
					Exit out of the tag. Try to kill any
					generated output.
				--->
				<cfset THISTAG.GeneratedContent = "" />

				<!--- Exit tag. --->
				<cfexit method="EXITTAG" />


			</cfif>

		</cfcase>

	</cfswitch>


	<!---
		ASSERT: If we have reached this point then we are
		either in the START mode of the tag or we have
		reached the END mode of the tag and we are definitely
		going to execute the tag at least once (for the
		given iteration).
	--->

</cfsilent>


<!---
	We only want the output stuff to happen in
	the end tag.
--->
<cfif (THISTAG.ExecutionMode EQ "End")>

	<cfoutput>

		<!--- Output the index. --->
		#THISTAG.Index#.

		<!---
			Loop over the possible MOD values that we
			collected earlier in the tag.
		--->
		<cfloop
			index="intMod"
			from="1"
			to="#ArrayLen( THISTAG.ModKeys )#"
			step="1">

			<!---
				Check to see if this key MODs evenly into
				the current iteration index.
			--->
			<cfif NOT (THISTAG.Index MOD THISTAG.ModKeys[ intMod ])>

				<!---
					We have a match. Output the value
					stored at this mod key.
				--->
				#THISTAG.ModValues[ THISTAG.ModKeys[ intMod ] ]#

			</cfif>

		</cfloop>

		<!--- Line break. --->
		<br />

	</cfoutput>


	<!--- Kill extra output. --->
	<cfsilent>

		<!--- Increment the tag index. --->
		<cfset THISTAG.Index = (THISTAG.Index + ATTRIBUTES.Step) />


		<!---
			Check to see which direction we are going in in
			the loop and if we can increment. If we can, then
			loop to the beginning of the tag.
		--->
		<cfif (
				(
					(ATTRIBUTES.Step GT 0) AND
					(THISTAG.Index LTE ATTRIBUTES.To)
				)
			OR
				(
					(ATTRIBUTES.Step LT 0) AND
					(THISTAG.Index GTE ATTRIBUTES.To)
				)
			)>

			<!---
				We still have more interations to perform. This
				will break out of the current tag execution an
				re-execute the tag keeping the same tag variables
				in place.
			--->
			<cfexit method="LOOP" />

		<cfelse>

			<!--- We must end the loop. --->
			<cfexit method="EXITTAG" />

		</cfif>

	</cfsilent>

</cfif>

So there you go. It was a lot of fun to write this and I was totally excited to finally get to use the LOOP directive for the CFExit tag - it's not often that that is useful. Plus, you can add any MODX attributes to the tag you want (where X is some integer).

I have been a long time listener to the ColdFusion weekly podcast and now, I am finally a participant. I may have lost this battle, but the next one is mine!

Want to use code from this post? Check out the license.

Reader Comments

15,663 Comments

Oh dear lord! Why are there like 275+ comments on that post :D That is nuts. Well, the guys on CFWeekly said they got the question off of some other pod cast - perhaps it was one that was talking about this FizzBuzz stuff.

6 Comments

What about:

<code>
<pre>
<cfscript>

for (a=1; a lte 100; a=a+1) {
writeoutput('<br>#numberformat(a,'000')# ');
modMe(a,3,' Coldfusion');
modMe(a,5,' Rocks');
}

function modMe(number,modno,message) {
if ( arguments.number mod arguments.modno eq 0 ) {
writeoutput(arguments.message);
}
return true;
}

</cfscript>
</pre>
</code>

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel