Articles

Pluralization Helper for C#

In .Net, Rails on October 28, 2009 by Matt Grande Tagged: , , ,

UPDATE – This code is now available on GitHub.  Click here for the repo.

I recently wanted a pluralization inflector in a C# project, one similar to the one in Ruby on Rails. Unable to find a satisfactory one, I whipped up my own. Here’s what I’ve got.

namespace MyNamespace
{
    public class Formatting
    {
        private static readonly IList<string> Unpluralizables = new List<string>
        { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" };
        private static readonly IDictionary<string, string> Pluralizations = new Dictionary<string, string>
        {
            // Start with the rarest cases, and move to the most common
            { "person", "people" },
            { "ox", "oxen" },
            { "child", "children" },
            { "foot", "feet" },
            { "tooth", "teeth" },
            { "goose", "geese" },
            // And now the more standard rules.
            { "(.*)fe?", "$1ves" },         // ie, wolf, wife
            { "(.*)man$", "$1men" },
            { "(.+[aeiou]y)$", "$1s" },
            { "(.+[^aeiou])y$", "$1ies" },
            { "(.+z)$", "$1zes" },
            { "([m|l])ouse$", "$1ice" },
            { "(.+)(e|i)x$", @"$1ices"},    // ie, Matrix, Index
            { "(octop|vir)us$", "$1i"},
            { "(.+(s|x|sh|ch))$", @"$1es"},
            { "(.+)", @"$1s" }
        };

        public static string Pluralize(int count, string singular)
        {
            if (count == 1)
                return singular;

            if (Unpluralizables.Contains(singular))
                return singular;

            var plural = "";

            foreach (var pluralization in Pluralizations)
            {
                if (Regex.IsMatch(singular, pluralization.Key))
                {
                    plural = Regex.Replace(singular, pluralization.Key, pluralization.Value);
                    break;
                }
            }

            return plural;
        }
    }
}

And of course, some NUnit tests.

namespace AutomatedTests
{
    [TestFixture]
    public class FormattingTests
    {
        [Test]
        public void StandardPluralizationTests()
        {
            var dictionary = new Dictionary<string, string>();
            dictionary.Add("sausage", "sausages");  // Most words - Just add an 's'
            dictionary.Add("status", "statuses");   // Words that end in 's' - Add 'es'
            dictionary.Add("ax", "axes");           // Words that end in 'x' - Add 'es'
            dictionary.Add("octopus", "octopi");    // Some Words that end in 'us' - Replace 'us' with 'i'
            dictionary.Add("virus", "viri");        // Some Words that end in 'us' - Replace 'us' with 'i'
            dictionary.Add("crush", "crushes");     // Words that end in 'sh' - Add 'es'
            dictionary.Add("crutch", "crutches");   // Words that end in 'ch' - Add 'es'
            dictionary.Add("matrix", "matrices");   // Words that end in 'ix' - Replace with 'ices'
            dictionary.Add("index", "indices");     // Words that end in 'ex' - Replace with 'ices'
            dictionary.Add("mouse", "mice");        // Some Words that end in 'ouse' - Replace with 'ice'
            dictionary.Add("quiz", "quizzes");      // Words that end in 'z' - Add 'zes'
            dictionary.Add("mailman", "mailmen");   // Words that end in 'man' - Replace with 'men'
            dictionary.Add("man", "men");           // Words that end in 'man' - Replace with 'men'
            dictionary.Add("wolf", "wolves");       // Words that end in 'f' - Replace with 'ves'
            dictionary.Add("wife", "wives");        // Words that end in 'fe' - Replace with 'ves'
            dictionary.Add("day", "days");          // Words that end in '[vowel]y' - Replace with 'ys'
            dictionary.Add("sky", "skies");         // Words that end in '[consonant]y' - Replace with 'ies'

            foreach (var singular in dictionary.Keys)
            {
                var plural = dictionary[singular];

                Assert.AreEqual(plural, Formatting.Pluralize(2, singular));
                Assert.AreEqual(singular, Formatting.Pluralize(1, singular));
            }
        }

        [Test]
        public void IrregularPluralizationTests()
        {
            var dictionary = new Dictionary<string, string>();
            dictionary.Add("person", "people");
            dictionary.Add("child", "children");
            dictionary.Add("ox", "oxen");

            foreach (var singular in dictionary.Keys)
            {
                var plural = dictionary[singular];

                Assert.AreEqual(plural, Formatting.Pluralize(2, singular));
                Assert.AreEqual(singular, Formatting.Pluralize(1, singular));
            }
        }

        [Test]
        public void NonPluralizingPluralizationTests()
        {
            var nonPluralizingWords = new List<string> { "equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "deer" };

            foreach (var word in nonPluralizingWords)
            {
                Assert.AreEqual(word, Formatting.Pluralize(2, word));
                Assert.AreEqual(word, Formatting.Pluralize(1, word));
            }
        }
    }
}

And finally, usage.

var output = Formatting.Pluralization(2, "item");
// Produces "items"
output = Formatting.Pluralization(5, "sheep");
// Produces "sheep"
output = Formatting.Pluralization(100, "sausage");
// Produces "sausages"
output = Formatting.Pluralization(1, "sausage");
// Produces "sausage"

Now, I’m sure that I’m missing some cases in there. For example, I haven’t found a good way to pluralize “proof.” If any of you wonderful people find another missing case, or if you want to add one, let me know in the comments.

Articles

Google Maps Street View in Hamilton!

In Uncategorized on October 8, 2009 by Matt Grande

A couple days back, Google Maps Street View was finally added to Hamilton. Here’s some of my favourite images:

Actually, that’s all the neat stuff I’ve seen around Hamilton so far… Not that much, come to think of it.

In other news, I start a new job in just over a week. Things will be hectic for the next while, so I won’t be posting as much. And I don’t really post all that much to begin with so… Things will be really bad.

Articles

Love vs. Hate

In lovestuffhatestuff.info, Rails on October 1, 2009 by Matt Grande

So, I created a website. Maybe you’d like to visit it? It’s just a quick little thing. You can see it here: lovestuffhatestuff.info

Articles

Delaying File Processing & Uploading with DelayedJob and PaperClip

In delayedjob, paperclip, Rails, Ruby, s3 on August 11, 2009 by Matt Grande

I’m going to say it right now. I love DelayedJob. I’ve been working on a project that involves uploading assets, mostly images, to S3 and resizing the images to three sizes. With the addition of DelayedJob, this can happen much faster.

Before I go on, I should mention that I wouldn’t have been able to delay paperclip without this post, and I wouldn’t have been able to delay S3 uploads without this post. Thanks go out to both bloggers!

The original DelayedJob was done by tobi. I’ll be using the version created by collectiveidea, which has a few extra nice features.

So the first step is to install it. I had to install both the plugin and the gem. The plugin was unable to generate the database migration, and the gem was unable to run the rake task. Hopefully, it was either a mistake on my part, or they’ll fix it soon.

script/plugin install git://github.com/collectiveidea/delayed_job.git
sudo gem install collectiveidea-delayed_job

Collectiveidea’s delayed_job has a nice generation script which will make your delayed_jobs table for you.

script/generate delayed_job
rake db:migrate

You’ll also want to add a processing column to your asset table.

class AddProcessingToAsset < ActiveRecord::Migration
  def self.up
    add_column :assets, :processing, :boolean, :default => true
  end

  def self.down
    remove_column :assets, :processing
  end
end

Your next step is to move into the code. To prevent the S3 upload, we subclass our Asset model with TempAsset, and just save locally.

class TempAsset < Asset
  has_attached_file :media, :path => ":rails_root/tmp/uploads/:id/:basename.:extension"
end

In your controller, save your TempAsset instead of Asset, and call a method that will queue up the processing.

class AssetsController < ApplicationController
  def create
    @asset = TempAsset.new(params&#91;:asset&#93;)
    @asset.save
    @asset.queue_move_to_s3
    redirect_to @asset
  end
end
&#91;/sourcecode&#93;

From there, we want to make our <code>queue_move_to_s3</code> method.  I'm using <code>send_later</code> in this example, but <code>enqueue</code> works just as well.  While we're at it, we'll write the method that will perform the saving.


# In temp_asset.rb
def queue_move_to_s3
  self.send_later(:perform)
end

def perform
  asset = Asset.find(self.id) # This is the same db record as self
  asset.media = self.media.to_file
  asset.processing = false
  asset.save!  # This will re-upload & re-size your image as per your has_attached_file method in Asset

  path = self.media.path
  self.media.to_file.close
  File.delete(path)
end

One last thing you may want to do is have a “We’re Still Processing This File” image. Thanks to the processing column we added earlier, it’s a piece of cake.

# In asset.rb
def url(style=:thumb)
  if processing
    still_processing_image_path
  else
    self.media.url(style)
  end
end

There are unfortunately a few hoops to jump through to get this working, but in the end you have a much faster response time which leads to a much better user experience. Hope this helps!

Articles

Features I’m Currently Infatuated with in Rails

In Rails, Ruby on August 5, 2009 by Matt Grande

I’ve recently started a new project in Rails and I’m trying to follow all the best practices. Sometimes at my company they have fallen by the wayside, but I’m really putting an effort into enforcing them on the team. In doing so, I’ve started using a few methods that I’ve known about for awhile, but never really had a chance to use.

Nested Routes

In a blog, Post has_many Comments, that’s a given. Wouldn’t it be nice if your URLs reflected this relationship? Now they can! In your routes file, simply add map.resources :post, :has_many => [:comments]. Then, change your paths from new_comment_path to new_post_comment_path(@post). In your CommentsController, you’ll have access to both params[:id] and params[:post_id] now.

While we’re talking about related objects, rather than creating objects like this:

@post = Post.find(params[:post_id])
@comment = Comment.new(params[:comment])
@comment.post_id = @post.id
@comment.save

Do something like this:

@post = Post.find(params[:post_id])
@comment = @post.comments.build(params[:comment])
@comment.save

This way, your association is automatically built.

The new and improved render method

This has been in Rails for awhile now, but I’m just now getting a chance to use it. I always hated doing this:

<% @comments.each do |comment| %>
  <%= render :partial => "comments/comment", :locals => { :comment => comment } %>
<% end %>

It seemed so overly verbose, having the singular and plural form of ‘comment’ in there six times. As of Rails 2.2 (I believe) there’s been a handy helper, though.

<%= render @comments %>

The loops through all of your comments and renders them into the partial ‘comments/_comment.html.erb’ and give them a variable name of comment. But what do you do when you’re in the partial? Well that brings me to my next point…

The joy of div_for

You’re in a comment partial. You want to wrap the comment in a div with a unique id. You want to give all your comments the same class. Don’t do this:

<div id="comment_<%= comment.id -%>" class="comment">
  <!-- display the comment --></div>

when you can do this:

<% div_for(comment) do %>
  <!-- display the comment -->
<% end %>

Nice and easy.

Articles

Installing Ruby 1.9 from sources on Ubuntu

In Ruby, Ubuntu on July 28, 2009 by Matt Grande

I found a few posts on how to do this, and all of them over-complicate things. Here’s a simple, step-by-step method to install ruby 1.9.1 from source.

First, ensure that the latest stable version is still 1.9.1-p129. If it isn’t, make sure to change the appropriate file names.

Step 1: Download the source.
wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p129.tar.gz

Step 2: Un-Tar
tar -zvxf ruby-1.9.1-p129.tar.gz

Step 3: Configure (aka, Make the makefile)
cd ruby-1.9.1-p129/
./configure

Step 4: Make
make

Step 5: Run the tests. This is optional if you hate tests.
make test

Step 6: Install Ruby
sudo make install

And that’s it! You’re done!

Articles

Project Euler #3

In Golf, Project Euler, Ruby on May 13, 2009 by Matt Grande

Here’s the next one. I’m not happy with the way I determine if it’s a prime number (checking the modulo of every possible number), but it works.

The prime factors of 13195 are 5, 7, 13 and 29.

What is the largest prime factor of the number 600851475143 ?

def find_largest_prime_factor(n)
  if n % 2 == 0
    find_largest_prime_factor(n/2)
  else
    x = find_divisor(n, 3)
  end
  puts x
end

def find_divisor(number, divisor)
  return divisor if divisor >= number
  if number % divisor == 0
    find_divisor(number/divisor, divisor)
  else
    find_divisor(number, divisor+2)
  end
end

find_largest_prime_factor(600851475143)

And the golf…

# Score: 98
def a n;p n%2==0?a(n/2):b(n,3);end;def b(n, d);d>=n ?d:n%d==0?b(n/d,d):b(n,d+2);end;a 600851475143

Articles

Project Euler #2

In Golf, Project Euler, Ruby on May 11, 2009 by Matt Grande

Here we go, Project Euler #2!

Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …

Find the sum of all the even-valued terms in the sequence which do not exceed four million.

one_back, two_back, current_fib, total = 1, 1, 0, 0</code>

while current_fib &lt; 4_000_000 do
two_back = one_back
one_back = current_fib
current_fib = one_back + two_back
total += current_fib if current_fib % 2 == 0
end

puts total

And the golf…

# Score: 69
a,b,c,t=1,0,0,0;while(c<4000000):b=a;a=c;c=a+b;t+=c if c%2==0;end;p t [/sourcecode] EDIT! I knew there was a way to do it without that stupid current_fib variable, but I couldn't get it to work at first. Then, as soon as I post, I realised what I was doing wrong. [sourcecode language="ruby"] one_back, two_back, total = 1, 1, 0 while one_back < 4_000_000 do total += one_back if one_back % 2 == 0 current = one_back one_back += two_back two_back = current end puts total [/sourcecode] And, even better, it improved my golf score! [sourcecode language="ruby"] # Score: 63 a,b,t=1,1,0;while a<4000000:t+=a if a%2==0;c=a;a+=b;b=c;end;p t [/sourcecode]

Articles

Jim Weirich on Connascence

In RailsConf, Ruby on May 11, 2009 by Matt Grande

I was recently at RailsConf in Las Vegas. For me, the stand-out best talk was Jim Weirich’s session on Connascence, “Writing Modular Applications.” I, unfortunately, couldn’t find a video of the talk he gave at RailsConf, but he gave the same talk at Mountain West Ruby Conf 2009, and you can watch a video of that here. I truly believe that this is one of those things that every programmer should watch. What are you doing still reading this? Go watch.

Articles

Project Euler #1

In Golf, Project Euler, Ruby on May 11, 2009 by Matt Grande

I recently signed up for Project Euler. I’ve decided to see how far I can get, both just solving the problems and “golfing” them. If you’re not familiar with programming golf, it’s when you try to write as few lines of code and characters as possible.

So far, I’ve only solved the first problem, as I’ve just started. I’m looking forward to doing more in the future. If you’re interested, here’s my solution, along with my golf solution.

# If we list all the natural numbers below 10 that are 
# multiples of 3 or 5, we get 3, 5, 6 and 9. The sum 
# of these multiples is 23.
# Find the sum of all the multiples of 3 or 5 below 1000.

total = 0
1000.times do |i|
  if i % 3 == 0 or i % 5 == 0
    total += i
  end
end
puts total
# Score: 45
t=0;1000.times{|i|t+=i if i%3==0||i%5==0};p t