My Secret Life as a Spaghetti Coder
home | about | contact | privacy statement
When looping over collections, you might find yourself needing elements that match only a certain parameter, rather than all of the elements in the collection. How often do you see something like this?

foreach(x in a)
   if(x < 10)

Of course, it can get worse, turning into arrow code.

What we really need here is a way to filter the collection while looping over it. Move that extra complexity and indentation out of our code, and have the collection handle it.

In Ruby we have each as a way to loop over collections. In C# we have foreach, Java's got for(ElemType elem : theCollection), and Coldfusion has <cfloop collection="#theCollection#"> and the equivalent over arrays. But wouldn't it be nice to have an each_where(condition) { block; } or foreach(ElemType elem in Collection.where(condition))?

I thought for sure someone would have implemented it in Ruby, so I was surprised at first to see this in my search results:

No search results for ruby each_where

However, after a little thought, I realized it's not all that surprising: it is already incredibily easy to filter a collection in Ruby using the select method.

But what about the other languages? I must confess - I didn't think long and hard about it for Java or C#. We could implement our own collections such that they have a where method that returns the subset we are looking for, but to be truly useful we'd need the languages' collections to implement where as well.

Of these four languages, ColdFusion provides both the need and opportunity, so I gave it a shot. First, I set up a collection we can use to exercise the code:

<cfset beer = arrayNew(1)>
<cfset beer[1] = structNew()>
<cfset beer[1].name = "Guiness">
<cfset beer[1].flavor = "full">
<cfset beer[2] = structNew()>
<cfset beer[2].name = "Bud Light">
<cfset beer[2].flavor = "water">
<cfset beer[3] = structNew()>
<cfset beer[3].name = "Bass">
<cfset beer[3].flavor = "medium">
<cfset beer[4] = structNew()>
<cfset beer[4].name = "Newcastle">
<cfset beer[4].flavor = "full">
<cfset beer[5] = structNew()>
<cfset beer[5].name = "Natural Light">
<cfset beer[5].flavor = "water">
<cfset beer[6] = structNew()>
<cfset beer[6].name = "Boddington's">
<cfset beer[6].flavor = "medium">

Then, I exercised it:

<cfset daytypes = ["hot", "cold", "mild"]>
<cfset daytype = daytypes[randrange(1,3)]>

<cfif daytype is "hot">
   <cfset weWantFlavor = "water">
<cfelseif daytype is "cold">
   <cfset weWantFlavor = "full">
   <cfset weWantFlavor = "medium">   

Flavor we want: #weWantFlavor#<br/><br/>
Beers with that flavor: <br/>
<cf_loop collection="#beer#" item="aBeer" where="flavor=#weWantFlavor#"><br/>

Obviously, that breaks because don't have a cf_loop tag. So, let's create one:

<!--- loop.cfm --->
<cfparam name="attributes.collection">
<cfparam name="attributes.where" default = "">
<cfparam name="attributes.item">
<cfif thistag.ExecutionMode is "start">
   <cfparam name="isDone" default="false">
   <cfparam name="index" default="1">   
   <cffunction name="_getNextMatch">
      <cfargument name="arr">
      <cfloop from="#index#" to="#arrayLen(arr)#" index="i">
         <cfset keyValue = attributes.where.split("=")>
         <cfset index=i>
         <cfif arr[i][keyValue[1]] is keyValue[2]>
         <cfreturn arr[i]>
      <cfset index = arrayLen(arr) + 1>
      <cfexit method="exittag">
   <cfset "caller.#attributes.item#" = _getNextMatch(attributes.collection,index)>
<cfif thistag.ExecutionMode is "end">
   <cfset index=index+1>
   <cfset "caller.#attributes.item#" = _getNextMatch(attributes.collection,index)>
       <cfif index gt arrayLen(attributes.collection)>
      <cfset isDone=true>
   <cfif not isDone>
      <cfexit method="loop">
      <cfexit method="exittag">

It works fine for me, but you might want to implement it differently. The particular area of improvement I see right away would be to utilize the item name in the where attribute. That way, you can use this on simple arrays and not just assume arrays of structs.

Thoughts anybody?

Hey! Why don't you make your life easier and subscribe to the full post or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!

Leave a comment

Looks like the code complexity went up, not down. That's why it is the way it is.

Posted by George on Nov 30, 2007 at 09:09 AM UTC - 5 hrs

Certainly having all that is a lot of work for one little if statement, but I was thinking that averaging it out over thousands of if statements would probably be worth it - sometimes that arrow code just makes it harder to read and that's one less indentation and test that you need to perform in your code, letting another piece handle it instead.

The CF case is worse than the others, as you don't need to define any new constructs in Java, C#, or Ruby.

Is that what you were referring to, or have I missed it?

Posted by Sammy Larbi on Nov 30, 2007 at 10:58 AM UTC - 5 hrs

Leave a comment

Leave this field empty
Your Name
Email (not displayed, more info?)


Subcribe to this comment thread
Remember my details

Picture of me

.NET (19)
AI/Machine Learning (14)
Answers To 100 Interview Questions (10)
Bioinformatics (2)
Business (1)
C and C++ (6)
cfrails (22)
ColdFusion (78)
Customer Relations (15)
Databases (3)
DRY (18)
DSLs (11)
Future Tech (5)
Games (5)
Groovy/Grails (8)
Hardware (1)
IDEs (9)
Java (38)
JavaScript (4)
Linux (2)
Lisp (1)
Mac OS (4)
Management (15)
MediaServerX (1)
Miscellany (76)
OOAD (37)
Productivity (11)
Programming (168)
Programming Quotables (9)
Rails (31)
Ruby (67)
Save Your Job (58)
scriptaGulous (4)
Software Development Process (23)
TDD (41)
TDDing xorblog (6)
Tools (5)
Web Development (8)
Windows (1)
With (1)
YAGNI (10)

Agile Manifesto & Principles
Principles Of OOD
Ruby on Rails

RSS 2.0: Full Post | Short Blurb
Subscribe by email:

Delivered by FeedBurner