James Williams
LinkedInMastodonGithub

Creating Autocomplete with Groovy and Ratpack

About a month ago, there were a couple of posts about making an autocomplete solution using tries. Inspired by it as an interesting engineering problem I decided to use the Python code from this post and create a solution with Groovy and Ratpack for a hack day at work.

A trie or prefix tree is a tree structure that generally stores string data. In the figure below, we can see a basic example of a trie that stores airport codes. Typing the characters in a node will return the children. For example, in the figure below, if I type the letter b, it should return BOI, BOS, and BWI but if I type "BO", it should return BOS and BOI.

Graphic of Tries with airport codes

For a seasoned developer, porting Python code to idiomatic Groovy is a breeze and I'll leave you to check out the zipped source or the original article which explains the insertion and lookup code very well.

Instead I'm going to focus on the steps needed to run it in Ratpack.

In my application class, I used JSON data from http://airports.pidgets.com/v1/ to setup my tries. I used a lightweight JSON parser/serializer called yajjl but feel free to drop in your preferred library or use org.json which is bundled with Ratpack.

root = new Trie(null,null)

def parser = new JsonParser()
def text = new File('worldairports.json').getText()
def airports = parser.parseArray(text)
airports.each {
    if (it.type.equals('Railway Stations'))
        if (it.name.equals(""))
            it.name =  it.city+", "+it.state+" Rail"
    def name =  it.name
    def location =  it.city+", "+it.state+", "+it.country
    def code = it.code
    root.insert(name.toLowerCase(), 
        [displayName:name, location:location, code:code])
}
//codes
airports.each {
    if (it.type.equals('Airports')) {
        def location =  it.city+","+it.state+","+it.country
        root.insert(it.code.toLowerCase(), [displayName:it.name,code:it.code, location:location])
    }
}

One of the initial constraints of this project is that it had to work with the YUI widgets we were using. Yes. YUI. I know.

The only endpoint I have to define is the endpoint that YUI hits asking for a list of autocomplete suggestions. It takes in a query string and returns a JSON list of objects.

post("/autoCompleteLocation") {
    def x = root.autocomplete(params.query)
    def list = []
    for (i in x) {
        list.add([
            value:i.extraData["displayName"], 
            text:i.extraData["displayName"]+", "+i.extraData["location"]
        ])
    }
    serializer.serialize(list)          
}

Including the code for the tries that I merely translated from Python to Groovy, it's only 132 lines of code. With minor tweaks, it could work with a JQuery autocomplete widget or be expanded to offer "Did you mean ...?"-style suggestions too.