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)
doSomething;
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:
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"
>
<cfelse>
<cfset weWantFlavor = "medium"
>
</cfif>
<cfoutput>
Flavor we want: #weWantFlavor#<br/>
<br/>
Beers with that flavor: <br/>
<cf_loop collection="#beer#"
item="aBeer"
where="flavor=#weWantFlavor#"
>
#aBeer.name#<br/>
</cf_loop>
</cfoutput>
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]>
</cfif>
</cfloop>
<cfset index = arrayLen(arr) + 1>
<cfexit method="exittag"
>
</cffunction>
<cfset "caller.#attributes.item#"
= _getNextMatch(attributes.collection,index)>
</cfif>
<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>
<cfif not isDone>
<cfexit method="loop"
>
<cfelse>
<cfexit method="exittag"
>
</cfif>
</cfif>
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 - 6 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 - 6 hrs
Leave a comment