- Published
- 4 March 2013
- Tagged
There's a particular coding idiom that's prevalent in Applescript:
set subSet to every item whose name is "bar"
Usually I have to surround various bits of the command with parentheses so Applescript knows what the hell I mean, but you get the overall pattern. I can specify a number of conditions, and AppleScript will fetch only the records that match said conditions. In much the same way as you'd put filters on a SQL query, you want to use filters because it's generally faster and easier than trying to do it through "proper" AppleScript.
rb-appscript
has a similar setup:
include Appscript
subSet = items[its.name.eq 'bar']
Although this gets messy when you have two or three conditions:
subSet = items[its.name.eq('bar').and(its.done.eq(true).and(its.size.gt(3)))]
Switching to MacRuby recently, one of the things I missed was this "filter" functionality. I couldn't find any examples anywhere of how to select every item whose name was "bar" without making a somewhat time-consuming select
loop:
subSet = items.get.select{ |i| i.name == 'bar' }
Thankfully, there is a way to do it in MacRuby, it's just a bit more complex.
The first step is to realise that MacRuby has overloaded the Object#methods
method to let you access Objective-C-style methods (more info here). Using the call methods(true,true)
on an array got me a whole heap of interesting methods you can now use on arrays, including the filterUsingPredicate
method. This method takes a NSPredicate
and uses it to filter your list of arrays - which can be considerably faster than the horrid select
loop suggested above.
You make a NSPredicate
in the following manner:
n = NSPredicate.predicateWithFormat %(name = "bar")
That's a string at the end, I'm just using alternate quotes to look cool and avoid having to escape my double-quotes. You can find out all about predicates here.
Now all I do is feed in my predicate to the array:
subSet = items.filterUsingPredicate(n)
How much faster is this than running a select
loop? I tried benchmarking it on my OmniFocus database:
!/usr/bin/env macruby
require 'benchmark'
framework 'ScriptingBridge'
of = SBApplication.applicationWithBundleIdentifier('com.omnigroup.omnifocus').defaultDocument
Benchmark.bm(20) do |x|
x.report('Predicate') do
n = NSPredicate.predicateWithFormat("completed = TRUE")
of.flattenedTasks.filterUsingPredicate(n)
end
x.report('Select') do
of.flattenedTasks.select{ |t| t.completed }
end
end
The result:
user system total real
Predicate 0.000000 0.000000 0.000000 ( 0.002507)
Select 0.130000 0.070000 0.200000 ( 6.323786)
Using select, it takes six seconds just to get the data. With predicate, you don't even notice. Quite an improvement.