« Home | Thats what being called Humble » | My Ruby Journey Has Started » | Its not 2 late to learn » | Consensus or Instinct » | Data Crunching - I cant forget these lines » | Keep It Simple and Sloppy - KISS » | Every cloud has a SILVER lining » | My First Tryst with Digital Asset Management » | Expense Calculator - Session 6 » | Expense Calculator - Session 5 »

I am Impressed. Mighty Impressed

On an earlier post I had mentioned that I will be comparing EzExpense java application by rewriting it in Ruby. But I thought let me start at a smaller scale and I went back to my numerology application.

Let me explain the application domain:

I meet a numerologist Janjay Sumani . He wants an application for the following. People come to him for advice on numbers. Given a person's name and birth date he computes some numbers and tells them what numbers they belong to, what are lucky, unlucky blah blah. Note by this I dont expect anyone to believe in numerology nor does the blogger.

Back to business I ask him give me an example : He says lets start with u.

Ur name: Deepak Manharlal Surti and Date of birth : 24 May 1977

He says we have a letter-number chart which tells the numeric value of each letter. We subsittue that for each word in a name. So Deepak => 455812. Now we find sum of digits : 455812 = > 25 => 7. Here he gives me a caveat: If the number is any of 11 , 22, 33, 44, 55, 66, 77, 88, 99 we dont reduce.

So lets do the calcs for Deepak Manharlal Surti
=> 455812 415512313 36241
=>25 25 16
=>7 7 7
=>21

So 21 is higher name number. Reduce 21 =>3 gives me lower name number. Haa now I understand I say.

Next we pick the birht date:
24 May 1977
=> 24 5 1977
=> 6 5 24
=> 6 5 6
=> 17

So 17 is my higher destiny number and 8 is my lower destiny number.

And just take the day you were born. 24. So 24 => 6 are my higher and lower material numbers respectively.

I start with app development using TDD (Test Driven Development) style and here's what I get:

I had written that app in Java with a web interface and a domain model. The classes in the domain
model were:

  • NumerologyCalculator.java (80 lines)--This was the central class. It calculated numbers.
  • NameManipulator.java (41 lines) -- This was to split a name into individual parts (viz Deepak Surti is split into [Deepak, Surti])
  • Letter.java(19 lines) -- A final class to hold letter values. A has value 1, D has value 4 etc in numerology. To look up these values.
  • DateGenerator.java(28 lines) -- A class to generate dates given a string. Note I didnt use java's Date class it was too complicated for my app. I instead created a custom MyDate class which comes next. Here i just need mth, year, day.
  • MyDate.java (31 lines) -- This had no responsibility. Its just a java bean.
  • UserInfoBean.java (84 lines) -- This was just a bean to hold user info viz his name, date and numbers.
Now lets add up . We get 283 lines of domain model code. Just to calculate numbers for a person given his name and date.

Further there was test code. Unit test which was 128 lines for the main TestNumerology.java. Here I tested only 6 birth dates and 3 names which I can say is not extensive coverage.
There were other test classes for other domain model objects which we ignore.

See all domain model code: Though its much better than procedural Java code that I have written in the past :-(( and thankfully have given up where I can, this code is still way 2 different from real business logic. How I wish my domain code and the business sat naturally with each other. That is if I could write code which read so much like the business. Anyways the code for u to see:


package com.deepaksurti.numerology.calculate;

import junit.framework.*;
import java.util.*;

import com.deepaksurti.numerology.calculate.BirthDate;
import com.deepaksurti.numerology.calculate.NumerologyCalculator;

/**
* Created by IntelliJ IDEA.
* User: Deepak Surti
* Date: Dec 10, 2001
* Time: 10:14:55 PM
* To change this template use Options | File Templates.
*/
public class TestNumerology extends TestCase{
NumerologyCalculator _calculator;
private BirthDate _birthDate1;
private BirthDate _birthDate2;
private BirthDate _birthDate3;
private BirthDate _birthDate4;
private BirthDate _birthDate5;
private BirthDate _birthDate6;
private String _name1;
private String _name2;
private String _name3;

protected void setUp() {
_calculator = new NumerologyCalculator();
_birthDate1 = new BirthDate(24, 5, 1977);
_birthDate2 = new BirthDate(14, 5, 1977);
_birthDate3 = new BirthDate(4, 5, 1977);
_birthDate4 = new BirthDate(24, 11, 1977);
_birthDate5 = new BirthDate(24, 12, 1977);
_birthDate6 = new BirthDate(28, 3, 1977);
_name1 = new String("Deepak Manharlal Surti");
_name2 = new String("Shilpa Pratap Shinde");
_name3 = new String("Manhar Balubhai Surti");
}

public void testMaterialNo() {
int material_no;

material_no = _calculator.calculateMaterialNo(_birthDate1);
assertEquals(material_no, 6);

material_no = _calculator.calculateMaterialNo(_birthDate2);
assertEquals(material_no, 5);

material_no = _calculator.calculateMaterialNo(_birthDate3);
assertEquals(material_no, 4);

material_no = _calculator.calculateMaterialNo(_birthDate6);
assertEquals(material_no, 1);
}

public void testHigherMaterialNo() {
int higherMaterialNo;

higherMaterialNo = _calculator.calculateHigherMaterialNo(_birthDate1);
assertEquals(higherMaterialNo, 24);
}

public void testDestinyNo(){
int destiny_no;
destiny_no = _calculator.calculateDestinyNo(_birthDate1);
assertEquals(destiny_no, 8);

destiny_no = _calculator.calculateDestinyNo(_birthDate4);
assertEquals(destiny_no, 5);
}

public void testHigherDestinyNo() {
int higherDestinyNo;

higherDestinyNo = _calculator.calculateHigherDestinyNo(_birthDate1);
assertEquals(higherDestinyNo, 17);

higherDestinyNo = _calculator.calculateHigherDestinyNo(_birthDate4);
assertEquals(higherDestinyNo, 23);
}

public void testMonthNo() {
int month_no;

month_no = _calculator.calculateMonthNo(_birthDate1);
assertEquals(month_no, 5);

month_no = _calculator.calculateMonthNo(_birthDate4);
assertEquals(month_no, 11);

month_no = _calculator.calculateMonthNo(_birthDate5);
assertEquals(month_no, 3);
}

public void testYearNo() {
int year_no;

year_no = _calculator.calculateYearNo(_birthDate1);
assertEquals(year_no, 6);
}

public void testNameNo() {

int name_no;

name_no = _calculator.calculateNameNo(_name1);
assertEquals(name_no, 3);

name_no = _calculator.calculateNameNo(_name2);
assertEquals(name_no, 5);

name_no = _calculator.calculateNameNo(_name3);
assertEquals(name_no, 1);
}

public void testHigherNameNo() {
int higherNameNo;

higherNameNo = _calculator.calculateHigherNameNo(_name1);
assertEquals(higherNameNo, 21);

higherNameNo = _calculator.calculateHigherNameNo(_name2);
assertEquals(higherNameNo, 14);

higherNameNo = _calculator.calculateHigherNameNo(_name3);
assertEquals(higherNameNo, 19);
}

}

-------------------------------------------------------------------------

package com.deepaksurti.numerology.calculate;

import com.deepaksurti.numerology.calculate.BirthDate;

import java.util.Collection;
import java.util.Iterator;

/**
* Created by IntelliJ IDEA.
* User: Deepak Surti
* Date: Dec 22, 2001
* Time: 9:34:16 AM
* To change this template use Options | File Templates.
*/
public class DateGenerator {
public static BirthDate generateBirthDate(String date) {
Collection splitUpDates = NameManipulator.locateWordsInName(date);
Iterator it = splitUpDates.iterator();
int dates[] = new int[3];
int index = 0;
while(it.hasNext()) {
String next = (String)it.next();
if(next.equals("")) dates[index++] = 0;
else
dates[index++] = (new Integer(next)).intValue();
}
return new BirthDate(dates[0], dates[1], dates[2]);
}
}

---------------------------------------------------------------------------

package com.deepaksurti.numerology.calculate;

import java.util.HashMap;

/**
* Created by IntelliJ IDEA.
* User: Deepak Surti
* Date: Dec 20, 2001
* Time: 12:57:55 PM
* To change this template use Options | File Templates.
*/
public final class Letter {
private static int _letterValues[] = new int[]
{1, 2, 3, 4, 5, 8, 3, 5, 1, 1, 2, 3, 4, 5, 7, 8, 1, 2, 3, 4, 6, 6, 6, 6, 1, 7};

public static int lookUpLetterValue(char letter) {
return _letterValues[letter - 65];
}
}

---------------------------------------------------------------------------

package com.deepaksurti.numerology.calculate;

import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;

/**
* Created by IntelliJ IDEA.
* User: Deepak Surti
* Date: Dec 20, 2001
* Time: 8:37:32 PM
* To change this template use Options | File Templates.
*/
public class NameManipulator {
public static Collection locateWordsInName(String name) {
Collection spaces = locateSpacesInName(name);
Iterator it = spaces.iterator();
Collection wordsInName = new ArrayList();
int beginIndex = 0, endIndex;
while(it.hasNext()) {
endIndex = ((Integer)it.next()).intValue();
wordsInName.add(name.substring(beginIndex, endIndex));
beginIndex = endIndex + 1;
}
wordsInName.add(name.substring(beginIndex, name.length()));
return wordsInName;
}

public static Collection locateSpacesInName(String name) {
name = name.toUpperCase();
char letter;
ArrayList spaces = new ArrayList();
for (int index = 0; index < letter =" name.charAt(index);">= 65 &&amp;amp;amp;amp;amp;amp;amp;amp; letter <=90) || (letter >= 48 && letter <= 57)){ continue; } else spaces.add(new Integer(index)); } return spaces; } } ---------------------------------------------------------------------------




The simple web interface looked like this :

Click to enlarge


Now I sit down with Ruby. And what I have. 1 tc_numerology.rb for test cases which is worth 93 lines but you know what it covers 68 assertions which is fantastic coverage. My complete test case is worth assertions.

And all my business aka domain logic is in just 1 numerology.rb file which is only 46 lines of code. So compare 46 to 283. Plus I was amazingly comfortable while writing ruby code. I felt like a child when I used to play with building blocks for hours and hours. Ruby is so much intutive.


And here comes the Ruby code: Compare this with java version above and you will see the difference for yourself:



module Numerology

Values = { "a" => "1", "b" => "2", "c" => "3", "d" => "4", "e" => "5", "f" => "8", "g" => "3",
"h" => "5", "i" => "1", "j" => "1", "k" => "2", "l" => "3", "m" => "4", "n" => "5",
"o" => "7", "p" => "8", "q" => "1", "r" => "2", "s" => "3", "t" => "4", "u" => "6",
"v" => "6", "w" => "6", "x" => "5", "y" => "1", "z" => "7"}


Power_numbers = [11,22,33,44,55,66,77,88,99]

class Calculator
def name_destiny_number(name)
names = name.split(/[^A-Za-z]/)
names.delete("")
names.each {|name| name.downcase!.gsub!(/[a-z]/) {|ch| Numerology::Values[ch]}}
numbers = names.collect {|name| name.to_i}
numbers = numbers.collect {|num| num = Numerology::number(num)}
numbers.inject(0) {|x, sum| sum + x}
end

def name_material_number(name)
Numerology::number(name_destiny_number(name))
end

def date_destiny_number(date)
Numerology::number(date.day) + Numerology::number(date.month) + Numerology::number(date.year)
end

def date_material_number(date)
Numerology::number(date.day)
end

end

def Numerology.sum(x)
x / 10 > 0 ? (x % 10) + sum(x / 10) : x % 10
end

def Numerology.number(num)
while num > 9 and !Numerology::Power_numbers.include? num
num = sum(num)
end
num
end

end



And my ruby test case looks like this:


require 'test/unit'

$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

require 'numerology'

require 'test/unit'

$:.unshift File.join(File.dirname(__FILE__), "..", "lib")

require 'numerology'

class TestNumerology < calculator =" Numerology::Calculator.new">

Now lets revisit the main code and see how it compares it with Janjay does::

class Calculator
def name_destiny_number(name)
names = name.split(/[^A-Za-z]/)
names.delete("")
names.each {|name| name.downcase!.gsub!(/[a-z]/) {|ch| Numerology::Values[ch]}}
numbers = names.collect {|name| name.to_i}
numbers = numbers.collect {|num| num = Numerology::number(num)}
numbers.inject(0) {|x, sum| sum + x}
end

See first line splits a name into its individual words (what Janjay Sumani does)
Next it removes any additonal spaces user may have entered (Janjay doesnt care)
Next we replace each character with its value from hash map
next we just convert strings like "455812" to a number 455812 (Janjay does these 2 steps looking into his chart)
Next for each number like we saw in example right at the top we get reduced number. The line numbers = numbers.collect {|num| num = Numerology::number(num)} does the magic.
And then i find the sum using inject method on arrays.

See didnt it feel like Janjay was at work and not some stupid obsucre compiler that makes you a better break dancer than Michaeal Jackson? I bet...

The great ideas whcih differentiate Ruby viz blocks have been used here. When you wanna learn focus a lot on its library, built in classes, modules, how to package using gems , and more importantly blocks and closures. When I move to write Tk interface I can explain closures better. Blocks originated with iterators but thats not their only usage.

I cant explain Ruby here. I have just started but it has got me hooked. I now wnat to write 3 interfaces for this : one using Rails (thats coming next mth), one using Tk and one using AJax. And we will compare these 3, time it takes blah blah to java.
I am already putting my money on Ruby.

Nice pointer to start Ruby this. Have Andy and Dave's book 'Programming Ruby' by your side. If you are really keen to make a difference you will learn Ruby. Its a great journey.

And yes I will test this language out more thoroughly with EzExpense a home finance calculator which has some crazy logic, which finance app doesnt have. That will be FUN.

Deepak as usual this article of yours was just great..and it gives a deep insight in what Ruby is and what it can do..I guess for developers this Ruby is a real gem...

I was just browsing and found your blog. Very Nice! I
have a hair loss solutions site. You can find everything about hair loss solutions as well as information on hair bows, color, extensions, and wigs. Please visit, check it out and enjoy!
Rod

Post a Comment