ruby

You are currently browsing articles tagged ruby.

I saw a post on comp.lang.lisp demonstrating the suitability of Common Lisp for functional programming. The poster asked to see versions in other languages including Ruby, so I thought I’d whip something up. Here’s the original post with description of the problem:

This one was too much fun for words in re how cool it is programming
with Lisp. I would like to see this in Ruby, Clojure, Qi, and
Scheme. The precise fun part tho is typing it all in in the final form
versus dividing the thing up into steps to get intermediate results,
ie, a test of one's mastery of one's language. Non-functional
languages I guess have no choice but to stop and assign temporaries.

Given:

(defparameter *pets*
  '((dog ((blab 12)(glab 17)(cbret 82)(dober 42)(gshep 25)))
    (cat ((pers 22)(siam 7)(tibet 52)(russ 92)(meow 35)))
    (snake ((garter 10)(cobra 37)(python 77)(adder 24)(rattle 40)))
    (cow ((jersey 200)(heiffer 300)(moo 400)))))

Write:

(defun digest-tag-population (tag-population pick-tags count)...)

Such that:

(digest-tag-population *pets* '(dog cat snake) 5)

=> ((DOG CBRET 82) (DOG DOBER 42) (CAT RUSS 92) (CAT TIBET 52) (SNAKE
PYTHON 77))

...the rules being:

- consider only the populations of tags (the first symbol in each
sublist) found in the parameter pick-tags, a list

- take only the  most populous of the union of the populations

- return (tag name population) of the most populous in this order:

    firstly, by position of the tag in pick-tags
    second, ie within a tag, in descending order of population

(defun subseq-ex (st e s)
  (subseq s st (min e (length s))))

(defun digest-tag-population (tag-population pick-tags count)
  (flet ((tagpos (tag) (position tag pick-tags)))
    (stable-sort (subseq-ex 0 count
                   (sort (loop for (tag population) in tag-population
                             when (tagpos tag)
                             append (loop for pop in population
                                        collecting (list* tag pop)))
                     '> :key (lambda (x)
                               (caddr x))))
      '< :key (lambda (x) (tagpos (car x))))))

(defparameter *pets*
  '((dog ((blab 12)(glab 17)(cbret 82)(dober 42)(gshep 25)))
    (cat ((pers 22)(siam 7)(tibet 52)(russ 92)(meow 35)))
    (snake ((garter 10)(cobra 37)(python 77)(adder 24)(rattle 40)))
    (cow ((jersey 200)(heiffer 300)(moo 400)))))

#+test
(digest-tag-population *pets* '(dog cat snake) 5)

And here is my Ruby version:

PETS = [
  [:dog, [[:blab, 12], [:glab, 17], [:cbret, 82], [:dober, 42], [:gshep, 25]]],
  [:cat, [[:pers, 22], [:siam, 7], [:tibet, 52], [:russ, 92], [:meow, 35]]],
  [:snake, [[:garter, 10], [:cobra, 37], [:python, 77], [:adder, 24], [:rattle, 40]]],
  [:cow, [[:jersey, 200], [:heiffer, 300], [:moo, 400]]]
]

def digest_tag_population tag_population, pick_tags, count
  tag_population.select {|e| pick_tags.include?(e[0]) }.
    inject([]) {|memo,obj| obj[1].each {|e| memo << [obj[0], e[0], e[1]] }; memo }.
    sort {|a,b| b[2] <=> a[2] }[0,count].
    sort_by {|e| [ tag_population.map{|p| p[0]}.rindex(e[0]), e[2] * -1] }
end

digest_tag_population(PETS, [:dog, :cat, :snake], 5)

Within the function:
Line 1: select elements that match the pick tags
Line 2: map to a list of tuples of the form [:dog, :blab, 12]
Line 3: sort the list of tuples by population and select the first count of them
Line 4: sort by tag position, population

Output:

[[:dog, :cbret, 82],
[:dog, :dober, 42],
[:cat, :russ, 92],
[:cat, :tibet, 52],
[:snake, :python, 77]]

I think Ruby compares very favorably. What do you think? Feel free to submit a version in another language.

Tags: , , , ,

I had a few mis-configuration issues when setting up shoulda and rcov for a new Rails 2.2.2 project, so I thought I’d jot down a few notes (mini tutorial, quickstart) to help save others from burning time on what should be a simple task.

shoulda is a library build on Test::Unit that provides helpers, macros and assertions to make testing easier.

rcov is a code coverage tool for Ruby.

1. Install rcov

sudo gem install rcov

2. Install shoulda

sudo gem install thoughtbot-shoulda --source=http://gems.github.com

3. Create your Rails project

rails myapp

4. Modify myapp/Rakefile

require(File.join(File.dirname(__FILE__), 'config', 'boot'))
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'
require 'shoulda/tasks'
namespace :test do
  desc 'Measures test coverage'
  task :coverage do
    rm_f "coverage"
    rm_f "coverage.data"
    rcov = "rcov -Itest --rails --aggregate coverage.data -T -x \" rubygems/*,/Library/Ruby/Site/*,gems/*,rcov*\""
    system("#{rcov} --no-html test/unit/*_test.rb test/unit/helpers/*_test.rb")
    system("#{rcov} --no-html test/functional/*_test.rb")
    system("#{rcov} --html test/integration/*_test.rb")
    system("open coverage/index.html") if PLATFORM['darwin']
  end
end

5. Modify myapp/test/test_helper.rb

...
# Add the following line
require 'shoulda/rails'   # require 'shoulda' also worked
...

Conclusion
After you’ve written some shoulda tests, you should be able to use the following rake commands:

rake test
rake test:units
rake shoulda:list    # display specs from shoulda tests
rake test:coverage   # run rcov and display code coverage

Tags: , , , , ,

Gospel Software LogoAlmost a year ago, I formed Gospel Software, LLC with two friends from my church. Our mission for the company is to develop web applications to help churches administratively.

It’s been a joy to work with Jordan and Scott over the last year and we’re now to the point of beginning to heavily promote our three web applications to churches. These are two men I can learn a lot from – both technically and spiritually. I would be hard pressed to find better business partners.

Gospel Software currently has three web applications.

Directory:

I just released a new version of the Gospel Software Directory a few minutes ago – there are some new screen shots to show some of the features. I had wanted a nice online photo directory for our church for quite a while. I finally wrote a simple bare bones version a few years ago and ended up using it all the time, so I thought there might be a market for the product.

Over the last year, I wrote a completely new version, and now each church member can edit their own information, upload new photos, etc., so the information is more current and the church administrative staff has less work to do. I still think one of the best features is simply being able to match the faces of people I’ve met with their names. It’s now available for churches to try out and purchase.

This new version is just the beginning. I have a long list of enhancements I’ll begin rolling out over the next few months.

GuestView:

Scott’s GuestView program is something I use regularly as I follow up with visitors to our church. It’s so handy and easy to use. I get an email when I need to call a visitor, then I can enter notes about our conversation, and if they’d like information from another leader in the church, I can notify the appropriate people.

SongBook:

I had thought about developing a program to manage worship songs back in the mid-eighties, but I was never motivated enough to do anything about it. When Jordan showed me his SongBook application, I was blown away – it did everything I had thought of and much more. And of course it was web based since the internet had been invented since I began thinking of a similar program :)

I’m excited about seeing what will be happening with Gospel Software, LLC this coming year.

We do have an affiliate program that rewards both the affiliate and any church they refer. Contact me for details if you’re interested.

The technical experiences we’ve had over the last year should provide for some interesting and informative blog posts in the future. When we came together to form the company, we had three products written in three different languages / frameworks. Integrating the three products together with a common infrastructure has been very educational :)

We now have a robust infrastructure that will support any future applications very well.

  • Server configuration, backup & light disaster recovery
  • Ecommerce – credit card processing, subscription management, invoicing, etc.
  • Auditing and event logging
  • Easy deployment of new releases
  • User management, authentication, authorization & accounting
  • And more…

As I mentioned, there are three languages / frameworks involved, but there is quite a bit of Ruby and Rails, and there will likely be more in the future. Each of the three languages / frameworks have their pros & cons, but I do feel that Ruby and Rails does very well in the evaluation.

The following are some things that I have been particularly pleased with:

  • My Macbook Pro with OSX and Emacs as a development environment
  • Ruby & Rails
  • nginx web server
  • mongrel application server
  • Postgres relational database
  • Trac issue tracker & wiki
  • Subversion source code control (possibly moving to git in the future, but for our purposes, svn has worked out very well)
  • Slicehost.com – being able to restart a VM on another server if hardware fails is awesome
  • Ubuntu Linux
  • istockphoto.com and fotolia.com for inexpensive stock photos
  • jQuery – it might not satisfy comp.lang.javascript, but it’s been great
  • Did I mention Emacs? :)

Tags: , , , , , , , ,

Ruby is a very flexible and expressive language. A recent question posted by a Ruby newbie got me looking through my IRC logs for a discussion about the performance of various dynamic method invocation approaches, so I thought I’d share some performance results.

Ruby is currently my primary programming language, and I like it a lot; however, the conciseness (except for blocks) and performance of its higher order function capabilities could be improved. Scheme and Haskell beat it in this regard.

Consider three versions of a higher order function, caller, and associated ways of providing information to it to allow invoking a method dynamically given a pre-existing object and argument:

# 1. lambda
def caller fn
  fn.call(obj, arg)
end

caller(lambda {|obj,arg| obj.meth(arg) })

# 2. send
def caller sym
  obj.send(sym, arg)
end

caller(:meth)

# 3. bind/call
def caller meth
  meth.bind(obj).call(arg)
end

caller(MyClass.instance_method(:meth))

A simple benchmark of the three approaches with results follows. The benchmark uses an elide(n) method that has been added to String:

lam = lambda {|s,n| s.elide(n) }
sen = :elide
met = String.instance_method(:elide)

bm(5) do |x|
  x.report('lambda') { 100_000.times { lam.call('abcdef',4) } }
  x.report('send') { 100_000.times { 'abcdef'.send(sen,4) } }
  x.report('meth') { 100_000.times { met.bind('abc').call(4) } }
end

           user     system      total        real
lambda 1.460000   0.510000   1.970000 (  1.982085)
send   0.810000   0.260000   1.070000 (  1.075201)
meth   0.660000   0.250000   0.910000 (  0.913455)

# results with the 3 preparation lines w/in their respective loops

           user     system      total        real
lambda 1.900000   0.590000   2.490000 (  2.498358)
send   0.800000   0.250000   1.050000 (  1.068035)
meth   0.760000   0.260000   1.020000 (  1.021275)

I personally like the lambda approach, but Ruby does impose a penalty for using it.

Tags: , ,

Peter Norvig wrote a simple spelling corrector in 20 lines of Python 2.5,
so I thought I’d see what it looks like in Ruby. Here are some areas I’m not pleased with:

  1. List comprehensions in Python made the edits1 function more elegant IMO.
  2. The boolean expression in the correct function evaluates empty sets/arrays as false in Python but not in Ruby, so I had to add the “result.empty? ? nil : result” expression to several functions. I expect there’s a better way to handle this also.

Otherwise, the translation was pretty straightforward.

Here’s a link to Norvig’s page:
http://www.norvig.com/spell-correct.html

That page includes a link to a text file that I saved locally as
holmes.txt: http://www.norvig.com/holmes.txt

def words text
  text.downcase.scan(/[a-z]+/)
end

def train features
  model = Hash.new(1)
  features.each {|f| model[f] += 1 }
  return model
end

NWORDS = train(words(File.new('holmes.txt').read))
LETTERS = ("a".."z").to_a.join

def edits1 word
  n = word.length
  deletion = (0...n).collect {|i| word[0...i]+word[i+1..-1] }
  transposition = (0...n-1).collect {|i| word[0...i]+word[i+1,1]+word[i,1]+word[i+2..-1] }
  alteration = []
  n.times {|i| LETTERS.each_byte {|l| alteration << word[0...i]+l.chr+word[i+1..-1] } }
  insertion = []
  (n+1).times {|i| LETTERS.each_byte {|l| insertion << word[0...i]+l.chr+word[i..-1] } }
  result = deletion + transposition + alteration + insertion
  result.empty? ? nil : result
end

def known_edits2 word
  result = []
  edits1(word).each {|e1| edits1(e1).each {|e2| result << e2 if NWORDS.has_key?(e2) }}
  result.empty? ? nil : result
end

def known words
  result = words.find_all {|w| NWORDS.has_key?(w) }
  result.empty? ? nil : result
end

def correct word
  (known([word]) or known(edits1(word)) or known_edits2(word) or
    [word]).max {|a,b| NWORDS[a] <=> NWORDS[b] }
end

After you’ve saved the holmes.txt file, load the code into irb and call the correct function with a string as follows:

badkins:~/sync/code/ruby$ irb
irb(main):001:0> require 'spelling_corrector.rb'
=> true
irb(main):002:0> correct "whree"
=> "where"

Tags: ,

Now that I’ve switched to a Macbook Pro with OSX Leopard as my primary desktop, I’ve located my Ubuntu machine in another part of the house to be accessible to my children. Not wanting to walk to the room where it’s located just to flip the power switch, I researched how to get “wake on LAN” working, so I could power it up remotely.

1. Enable the appropriate setting in your BIOS. Mine had something to do with wake on PCI device.

2. Install ethtool if you don’t already have it.

sudo apt-get install ethtool
cd /etc/init.d
sudo vim wakeonlanconfig

Add the following lines to that file:

#!/bin/bash
ethtool -s eth0 wol g

Install the script:

sudo update-rc.d -f wakeonlanconfig defaults

Run the script:

sudo /etc/init.d/wakeonlanconfig

3. Keep the network interface alive after shut down.

sudo vim /etc/init.d/halt

Change the following line:

halt -d -f -i $poweroff $hddown

to the following line (i.e. remove the -i)

halt -d -f $poweroff $hddown

4. Get the MAC address

ifconfig | grep HW

5. Send the magic packet via the following Ruby program:

require 'socket'
mac_addr = "x21x53x39xB3x90x42"
s = UDPSocket.new
s.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
s.send("xff"*6 + mac_addr*16, Socket::SO_BROADCAST, '10.0.0.255', 7)

Tags: , , , , ,

One of the reasons I haven’t been blogging much lately is because I’ve decided to bifurcate my blog into a professional/technical blog (which will continue here on lojic.com/blog) and a personal blog, and until I’ve decided on the technology to use for my personal blog I’ve been reluctant to blog much.

The motivation for the split is the feeling that a lot of my non-technical family & friends grow weary of weeding through a lot of techno-geek material to find anything interesting, and folks who read my blog for technical info probably don’t want to weed through the silly videos, etc.

Wordpress has worked fine for my blog thus far, but I want to take the opportunity to develop my personal blog in a new technology more for the learning experience than necessity. I haven’t had time to select the appropriate technology, so I have a bit of analysis paralysis.

The candidates are:

  • Ruby on Rails: I currently develop primarily in Ruby on Rails, so in that respect it would be the logical choice and easiest way to get started; however, it wouldn’t have the benefit of learning a new technology.
  • Arc: I had high hopes for Arc when Paul Graham first released it. I still think it has potential, but that potential is limited by Paul’s interest level and available time. It’s been over 3 months since the last release and that was only a small incremental improvement. The forum seems dead, and the fact that Arc went through a 5 year blackout period makes me wonder whether it will be a dead-end language and a waste of valuable time.
  • Common Lisp: I am leaning toward a Lisp, so if Arc doesn’t pan out, Common Lisp would be a good fallback language. It’s much more mature with robust implementations. It doesn’t provide a nice batteries included experience though, and I’ve been reluctant to collect the necessary libraries from various sources to allow anything remotely similar to Ruby on Rails with respect to ease of development. I think it may have a greater long term potential though, so it may be worth the effort.
  • Scheme: The PLT web server may give me a head start on a Lisp based web site, and Arc is based on MZScheme, so it’s on the short list.
  • Haskell: I know very little Haskell (even less than Lisp which is not much), but I’m intrigued by many aspects of the language. GHC seems to be a great compiler that produces well performing programs. My initial impression is that it will take more effort to learn than a Lisp, but in terms of brain stretching, it has a lot to offer. There is a Haskell based web server available, but like a lot of fringe languages, it appears to be pretty rough around the edges.

I have a vacation coming up, so I think I’ll use some of the down time to do some research and make a decision. Look for the blog bifurcation to happen in the latter half of June. If you have any opinions on the matter, please add a comment :)

Tags: , , ,

I came across a web site recently that prompted me to enter two names before I could view the site. So I wondered how difficult it would be to write a Ruby program that could pass the challenge. As you’d expect, it’s trivial in Ruby. If you encounter such a site, please don’t use the code in part two unless it’s permitted by the terms of service of the site in question.

Part One – Obtain a file of names

I found a government site that provided lists of the 1,000 most popular names for each decade, so I wrote a Ruby program to screen scrape the names into a list in order of popularity.

Make the open-uri library available.

require 'open-uri'

Create a simple Struct to contain the url for each decade along with an array for boy names and girl names (the site provides an ordered list for each gender). Then create an array of Decade instances for 80’s, 90’s and 00’s.

Decade = Struct.new(:url, :boy_names, :girl_names)
decades = [
  Decade.new('http://www.ssa.gov/OACT/babynames/decades/names1980s.html', [], []),
  Decade.new('http://www.ssa.gov/OACT/babynames/decades/names1990s.html', [], []),
  Decade.new('http://www.ssa.gov/OACT/babynames/decades/names2000s.html', [], [])
]

Iterate over each decade and screen scrape the list of boy names and girl names.

decades.each do |decade|
  str = open(decade.url).read
  str.scan(/<tr><td align="right">.*?<td width="15%">(w+)</td>.*?<td width="15%">(w+)</td>/m) do
    decade.boy_names << $1
    decade.girl_names << $2
  end
end

Now we have a pair of lists (boys & girls) for each decade with each list in order of popularity – 6 lists in total. We want to form a single list by selecting the most popular name from each of the 6 lists, followed by the second most popular name, etc. The zip function comes to mind, so we’ll first create an array of the 6 lists.

name_lists = decades.inject([]) do |result, decade|
  result << decade.boy_names
  result << decade.girl_names
  result
end

Now we have a list containing 6 lists. Here is where the "everything is an object" aspect of ruby is a bit of a pain. What I want to do is simply zip the 6 lists together, for example:

x = zip(one, two, three, four, five, six)

but I'm forced to pick one of the lists to be the instance for the zip method, for example:

x = one.zip(two, three, four, five, six)

I suppose my dissatisfaction is due in part to my recent research in functional programming.

The line below will do the following (I really like Ruby :) ):

  1. pop one of the lists off of the main list (leaving 5) to be the instance for zip
  2. explode the list of 5 remaining lists into 5 individual arguments via the * operator
  3. zip the 6 lists together
  4. flatten the resulting list of lists produced by zip into one list
  5. remove duplicate entries
  6. write each name to standard output
(name_lists.shift).zip(*name_lists).flatten.uniq.each {|name| puts name }

Run the program as follows to create a file named names.txt which will be used in part two.

ruby scrape_names.rb > names.txt

Part Two - Brute force form submission

Now that we have a list of over 2,000 names, it's a simple matter of repeatedly trying pairs of names in order of popularity. Here's the program in its entirety with comments following:

 1  require 'net/http'

 2  names = ARGF.inject([]) {|result, line| result << line.chomp; result }

 3  Net::HTTP.start('www....com') do |query|
 4    headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
 5    (1...names.length).each do |i|
 6      (0..i).each do |j|
 7        response = query.post('/guess/',
 8          "name1=#{names[i]}&name2=#{names[j]}", headers)
 9        if !response.body.include?('Failed')
10          puts "Answer: #{names[i]}, #{names[j]}"
11          exit
12        end
13      end
14    end
15  end

Line 1: makes the HTTP library accessible.

Line 2: reads a list of names from standard input into an array.

Line 3: opens a TCP connection to the specified web server (site withheld for privacy).

Line 4: through trial and error I discovered that contrary to the example in the Pick Axe p. 700, I need to add this header to get the form submission to work.

Lines 5-6: setup the iteration to compare each name (beginning with the second) with each of the previous names up to and including the current one (in case the answer is two identical names).

Lines 7-8: perform an HTTP post with the pair of names.

Lines 9-11: if the retrieved page doesn't contain a failure string, print the solution to standard output and exit.

Run the program as follows:

ruby guess_names.rb < names.txt

There you have it - a pair of Ruby programs to pass a "guess two names to enter" challenge. The amount of time to find a solution will depend on the uncommonness of the names used. This is an O(n^2) algorithm, so if the names in the solution are near the bottom of the popularity scale, expect a long run time. The name list I produced had 2,697 names, so the worst case scenario is (2697 * 2698) /2 -1 = 3,638,252 requests. If only the first 100 names need to be compared there would be 5,049 requests.

This algorithm is trivial to parallelize - just change line 5 above from (1...names.length) to multiple non-overlapping ranges (one per process), and let her fly.

Ruby has many benefits and is currently my most productive programming language, but I think it particularly shines in this type of ad-hoc, web-enabled task because of the expressiveness of the language and the availability of useful libraries. I highly recommend putting it high on your list of languages to learn if you don't already use it.

Tags: ,

Someone posted a question on comp.lang.ruby recently asking for help with solving anagrams. The poster originally asked about ways of generating permutations and several people pointed him to the facets library which has some permutation utility functions. As it turns out, I benchmarked the following naive permutation generator as 3 times faster than the facets library code:

def words chars, result='', &b
  if chars.empty?
    yield result
  else
    chars.length.times do |i|
      c = (x = chars.clone).slice!(i)
      words(x, result + c, &b)
    end
  end
end

However, generating all possible combinations of letters in an anagram is a very bad approach which doesn’t take into consideration key knowledge about the problem. Rather than generating all permutations of an anagram, which is infeasible for longer words, we can precompute a hash from our dictionary by using the sorted letters of words as keys. Then to solve an anagram, we simply sort the letters in the anagram and look it up in the hash.

The first program creates the hash and serializes it to disk for future anagram solving:

words = Hash.new([])

File.open("/usr/share/dict/words", "r") do |file|
  while line = file.gets
    word = line.chomp
    words[word.split('').sort!.join('')] += [word]
  end
end

File.open("word_hash", "w") do |file|
  Marshal.dump(words, file)
end

The second program loads the serialized hash from disk to solve anagrams that we type at the console:

words = nil

File.open("word_hash", "r") do |file|
  words = Marshal.load(file)
end

while true
  print "Enter word: "
  anagram = gets.chomp
  sorted_anagram = anagram.split('').sort!.join('')
  p  words[sorted_anagram]
end

This is really fast, but there are probably better methods. If you have one, feel free to add a comment with the code.

Tags: , ,

« Older entries § Newer entries »