Writing a Searchable Dictionary with Ruby and wxWidgets
Written By: Nathan Baker
- 29 May 2006 -
Description: For today's Ruby lesson, we will explore the creation of a simple GUI, and then create an interface allowing us to search a user-created dictionary.
Part 2: Getting started
OK, before we dive into the wide world of wxWidgets, let's get the backend out of the way first. We want to take a user-supplied dictionary file, read it into memory, and provide the ability to search it. Let's go with a simple dictionary format: the word (or phrase), followed by an arbitrary number of tabs, followed by the definition. This allows the file to be nicely-formatted but still easily parsed, and tab is one of the few characters that doesn't legitimately appear in most standard text of any language.
Anyone with any familiarity with Ruby and regular expressions (or even with my previous article on Ruby) should be able to do this in their sleep, so I'll just dump the whole class here and then point out the more interesting parts before moving on.
class Dictionary def initialize(file) = Hash.new File.open(file){ |f| parse(f.read()) } end def [](idx) [idx] end def lookup(word) [word] end def add(word, definition) [word] = definition end def each .each{ |k, v| yield(k,v) } end def has_key?(k) .has_key?(k) end private def parse(str) re = /(^[^\t]+)\t+(.+)/ str.scan(re){ |key, entry| [key] = entry } end end
Well, that was pretty simple, no? Let's dissect this a bit before we get to the fun stuff:
First off, you'll notice that I pass a block of code to File.open (if you're new to Ruby, note now that you'll be seeing this a lot). This is similar to a using block in C#. It automatically closes the file once the end of the block is reached. This does keep the file open throughout the parsing, which could be avoided if I read the file contents into a variable and then closed the file before passing the variable to the parse method, but this is a minor detail and I wanted to illustrate this technique.
You can also see that I treat this class like a container--I implement each and the subscript operator. If you're going to be providing a wrapper for a class, you need to implement the most important functions of that class or nobody will use your wrapper. Also, Ruby (characteristically) makes it really easy to do, so why not?
Also, note that the ? is a valid character in the name of a method, and is usually used to denote a predicate, or a method that returns true or false. Ruby's LISP roots are certainly evident here.
Finally, notice that I use the extremely handy scan method to parse the file. I define a regular expression, and when I pass it to the scan method it calls the following code block on each match generated by the regular expression. Even more handy, each parameter in the lambda expression is assigned the result of one parenthesized subexpression, which makes doing things like, oh, parsing dictionary files ridiculously easy.
OK! Text parsing is some of the most boring programming imaginable, and thankfully Ruby makes it pretty painless. Even so, I almost fell asleep while writing this, so let's shake things up a bit in the next section.