chris blogs

May 2005

09may2005 · Scripting for Runaways

During our trip over the extended weekend, we quickly decided it would be easier if different persons buy various stuff everyone needed (say, group tickets, lunch and booze). So, everyone started to pay lots of different things with his/her own money, and it seemed like it would get a big mess.

Ruby to the rescue! I quickly (and I mean it, I coded while the others made breakfast) came up with a script to calculate who needed to give what amounts of money to other persons so everyone spent the same amount of money—all in all.

In the beginning, it was a very easy task. We created a simple file formatted like this:

# Name      Amount  Comment
Mr. Foo     72.00   Tickets
Lil' Bar    33.00   Booze

The people that didn’t pay anything just were listed, with empty amount and comment:

Squarehead
J. Random Hacker

Now, we would run the program and get an output like that:

J. Random Hacker -> Lil' Bar: 8.25
J. Random Hacker -> Mr. Foo: 18.00
Lil' Bar -> Mr. Foo: 9.75
Squarehead -> Lil' Bar: 8.25
Squarehead -> Mr. Foo: 18.00

4 Leute, 26.25 pro Person (Cashflow: 105.00)
Lil' Bar: Booze
Mr. Foo: Tickets

On a glance, you see who needs to pay whom what amount and what for. Some throwaway results are presented too.

Of course, that script was far too easy (the initial version didn’t sort the lines, so it was even easier, but I didn’t use VC, so it’s lost in the mists of time). Have a look at the source.

Soon, trouble started. One person came on the next day on her own, so she wouldn’t need to pay the group tickets (that’s only fair), but of course the other stuff we bought. I simply rewrote the program to divide the money among the known people at that time, and list everyone at the beginning and the one that came late just later. It worked fine and was a quick adjustment.

On the second day, we decided to make the first cashing-up. Obviously, we wouldn’t want to remove the entries so far (they were needed for the statistic, too), but they shouldn’t be calculated anymore. I hacked some code to make --- output the list and delete the current debts.

Then, there was someone that would leave sooner, and of course didn’t want to pay things she couldn’t make use of. Another hack, and -Name lines remove the person from the known people. This messes up the statistic a bit, but we didn’t really care.

The source code of our final version is available too.

Now, when I revisit the code, it’s very much like a awk script to me. You can see the typical elements, no additional classes were defined, only hashes and arrays store the data. The main program is a loop to parse, and run stuff at the same time. It’s rather icky with it’s linear, but messed flow.

But I can do better. Let’s build a domain specific language to manage group money! Now, the input file (a valid Ruby program, actually) looks like this:

person :MrFoo
person :LilBar
person :Squarehead
person :JRHacker

MrFoo.pay  72.00, "Tickets"
LilBar.pay 33.00, "Booze"

cashing_up!

The last version (for now, at least) is only two lines longer than the previous one, but lets Ruby do the parsing. This is actually very useful. For example, we could now also write:

EGG = 0.33
JRHacker.pay    12*EGG, "a dozen eggs"

There are further ways to clean up the code. For now, shared state is saved in constants and globals, we could create a wrapper class to take care of that (even instance_eval the file inside it, and use $SAFE), but for now, and especially for such a simple program, this is not needed.

I have demonstrated the usefulness of a general-purpose scripting language to ease tasks that do happen on the road and wouldn’t be a lot of fun to calculate manually (YMMV, but if you have lots of entries, you need to calculate a lot). Additionally, I showed how a flexible object-oriented language like Ruby can be used to create mini-languages to save parsing overhead and make use of the language features.

I wish you well on your travels
My friends I wish you well along the way
— Dan Bern, Jail

NP: Manu Chao—La Despedida

Copyright © 2004–2013