Friday, July 31, 2009

My 73 lines of code

The perl code provided below is used for converting a numeric value to string value in bahasa Indonesia which is commonly used in quittance.


#!/usr/bin/perl

use strict;

my $value = ($ARGV[0])? $ARGV[0] : 1101000;
print STDOUT &terbilang($value),"\n";

exit;

sub terbilang {
my $value = shift;
my $result = '';
$value += 0;
my @part = ();
while($value) {
push(@part, substr($value, -3, 3));
substr($value, -3, 3, '');
}
@part = reverse @part;
for(my $i=0; $i<=$#part; $i++) {
(length($part[$i])==1) and $result .= &_terbilang1($part[$i]).' '.(($part[$i] ne '000')? &_terbilang_satuan($#part-$i).' ' : '');
(length($part[$i])==2) and $result .= &_terbilang2($part[$i]).' '.(($part[$i] ne '000')? &_terbilang_satuan($#part-$i).' ' : '');
(length($part[$i])==3) and $result .= &_terbilang3($part[$i]).' '.(($part[$i] ne '000')? &_terbilang_satuan($#part-$i).' ' : '');
}
$result =~ s/(?<!puluh |ratus )satu ribu/ seribu/g; # replace 'satu ribu' with 'seribu', except when 'puluh' or 'ratus' precedes 'satu ribu'
$result =~ s/\s{2,}/ /g; # replace extra space with one single space
$result =~ s/\s+$//g; # replace extra space in the end of line
$result =~ s/^\s+//g; # replace extra space in the beginning of line
$result =~ s/\b(\w)/\u$1/g; # capitalize first letter in every word
return $result;
}

sub _terbilang_satuan {
($_[0] == 1) and return 'ribu';
($_[0] == 2) and return 'juta';
($_[0] == 3) and return 'miliar';
($_[0] == 4) and return 'triliun';
}

sub _terbilang3 {
my $value=shift;
my $result = '';
$result .= (substr($value,0,1) eq '1')? 'seratus ' : ((substr($value,0,1) ne '0')? &_terbilang1(substr($value,0,1)).' ratus ':'');

if(substr($value,1,1) eq '0') {
$result .= &_terbilang1(substr($value,2,1));
} else {
$result .= (substr($value,1,1) eq '1')? &_terbilang2(substr($value,1,2)) : &_terbilang1(substr($value,1,1)).' puluh '.&_terbilang1(substr($value,2,1));
}
}

sub _terbilang2 {
(substr($_[0],0,1) eq '0') and return &_terbilang1(substr($_[0],1,1));
if (substr($_[0], 0, 1) eq '1') {
(substr($_[0], 1, 1) eq '0') and return 'sepuluh';
(substr($_[0], 1, 1) eq '1') and return 'sebelas';
return &_terbilang1(substr($_[0],1,1)).' belas';
} else {
return &_terbilang1(substr($_[0],0,1,)).' puluh '.&_terbilang1(substr($_[0],1,1));
}
}

sub _terbilang1 {
($_[0] eq '1') and return 'satu';
($_[0] eq '2') and return 'dua';
($_[0] eq '3') and return 'tiga';
($_[0] eq '4') and return 'empat';
($_[0] eq '5') and return 'lima';
($_[0] eq '6') and return 'enam';
($_[0] eq '7') and return 'tujuh';
($_[0] eq '8') and return 'delapan';
($_[0] eq '9') and return 'sembilan';
}


Who needs 172 lines when the same result can be accomplished with only 73 lines of code ;)

Wednesday, September 14, 2005

To Regex or not to Regex

Regular Expression (Regex) is undoubtful a strong feature of Perl. Many people use Perl if they want to do pattern matching, like in matching DNA result, matching human finger print, etc. However, Regex sometimes is not the fastest way. In some simple cases, Regex can be avoided by using built-in Perl functions, like index and substr. Note: index is very fast since it uses a Boyer-Moore algorithm.

Here is a simple example when Regex is found to be slow.

&call_a_function() if $condition =~ m/^yes$/i;


Function 'call_a_function()' will be called when $condition is 'Yes', 'yes', 'YES', 'YeS', or any other case combination. Using Regex for such a simple task is avoidable. You can also write the following code to do the same task:

&call_a_function() if lc($condition) eq 'yes';


Now, we are going to see some examples of Regex-replacement in a more complex situation.

my $sentence='I like apples, oranges, and bananas';


To check the string whether $sentence has the word 'apples' in it, we usualy use this command:

($sentence=~m/apples/ and print 'exist') or print 'not exist';


To save some time, we can replace the command above with this command:

(index($sentence, 'apples')!=-1 and print 'exist') or print 'not exist';


The function index() will return the position of the first existing occurence (starting from zero). It will return -1 if no match is found.

In my computer, index() is 38% faster than the regex method.

What about string replacement, is it possible to use Perl built in function? For some simple task, it is possible. Let's say we have a sentence and we want to replace a specific string with another string. See the example below:


my $sentence='I like classic and jazz. Do you like jazz?';
my $string='jazz';
my $word='metal';


To replace the first word 'jazz' with 'metal', we usually use this command:

$sentence=~s/$string/$word/;
# now $sentence is 'I like classis and metal. Do you like jazz?'


We can also use substr function as shown below:

substr($sentence, index($sentence,$string), length($string), $word);
# it will give the exact same result


The 'g' regex modifier will replace all 'jazz's with 'metal':

$sentence=~s/$string/$word/g;
# $sentence is 'I like classic and metal. Do you like metal?'


Can it be accomplished by using substr function? It is possible, but perhaps you won't like it :)

(substr($sentence,index($sentence,$string), length($string),$word)) while(index($sentence,$string)!=-1);


Since it is less readable than the regex method, you might want to put it inside a subroutine and call the subroutine whenever you need a simple string replacement as shown above.

Saturday, August 20, 2005

Comparing Fetching Database Query into Arrays and Arrayrefs

Since more and more real life problems are being automated by using computers, computer pograms are made to simplify our lifes. For some complex situation, complex database design is needed. In fact, in my company almost all our applications are storing into and retrieving from database intensively.

Sometimes the speed of retrieving data from database is the main issue in our applications, especially in creating reports. There are many ways to shorten your query time, from optimizing your SQL query string to upgrading the database system to a more sophisticated computer.

I have already found many articles in the Internet about optimizing your SQL query string. However, this time I am going to focus on using the right method from the database driver side which are used for accessing database. By using the right method, we can save some time.

As you may have known, in Perl we can use the DBI module to connect to our database and use the corresponding database driver to access a specific database application in object-oriented programming (OOP) style. For example, DBD::mysql and DBD::Pg are used for accessing MySQL and PostgreSQL database accordingly.

For the example below, I am assuming that you already know how to establish a connection to your database. Here is an example of OOP source code to retrieve some rows from database and print it to STDOUT.

my $sth=$main::dbh->prepare('SELECT name FROM product WHERE barcode LIKE \'899%\'');
$sth->execute() or die($sth->errstr);
while(my @row=$sth->fetchrow_array()) {
print $row[0],"\n";
}

Nothing's wrong with the source code above. It will work just fine. :)

The code starts with preparing the SQL query string, executing the query, and fetch the result rows into an array @row. I think this is the most often and widely used method to retrieve data.

When I first programmed using database, I wrote something similar to the code above almost all the time whenever I need to retrieve data from database. However, there is another method which can increase the speed up to 18% by changing only two lines. Amazing, isn't it?

Here are some slight changes I make:

my $sth=$main::dbh->prepare('SELECT name FROM product WHERE barcode LIKE \'899%\'');
$sth->execute() or die($sth->errstr);
while(my $row=$sth->fetchrow_arrayref()) {
print $$row[0],"\n";
}

In the method above, I use 'fetchrow_arrayref' instead of 'fetchrow_array'. This changes fetch the result rows into an array reference $row (instead of an array).

Surprisingly this small changes can really shorten data retrieval process. Do you wonder why? I think because when you use an array reference, you create a variable which refers to the value. On the other hand, when you use an array, you create a variable which is a copy of the original value. So, there are indeed 'hassles' in creating and dumping variables again and again. By using 'fetchrow_arrayref', we can avoid this thing.

If you then ask me how the fastest method will be, I believe that in some complex situation, SQL query string optimization still holds the biggest proportion. So, go learn SQL. :))

Friday, August 19, 2005

if-else

While some programmers emphasize writing codes which have more readability, I prefer writing codes which are much much more efficient. Why? Because it makes your program run faster and 'smoother' and it also shows your quality as a programmer.

If you have done programming work before, I am sure you have used the if-else statement. However, I really think that if-else statement is sometimes inefficient. For some simple cases (like the one I am about to demonstrate below), if-else statement are found to be less efficient.

A very simple example of if-else statement:

my $x=0;
my $string='';

if ($x > 10) {
$string='bigger than ten';
} else {
$string='lesser than or equal to ten';
}

Note: this code is used just for demonstration purpose only. It's almost impossible that you will need exactly the same code in your program :)

The simple task above can also be done in one simple line by using the ?: operator.

my $x=0;
my $string='';

$string=($x > 10)? 'bigger than ten' : 'lesser than or equal to ten';

I used the Benchmark module to compare code efficiency for each method. In my computer, the ?: operator is 13% faster than the if-else method. Not only does it shorten your program runtime, but also shortens your time typing your program code. :)

Below is my favorite, by using 'and' and 'or'. Unfortunately I seldom see it being used. However, I use it quite often in my programs.

my $x=0;
my $string='';

($x > 10 and $string='bigger than ten') or $string='lesser than or equal to ten';

At first, it may seem strange and unusual, but wait until you get used to it, you will love it! :) By using the benchmark module, in my computer I found this 'and/or' method to be 29% faster than the if-else method.

As you can see, the 'and/or' method is the fastest to do the simple task. However, if you don't like it or find it less readable, I still recommend the ?: operator. Avoid using if-else statement for such an easy and simple task. The ?: operator is better and the 'and/or' method is the best in my opinion. :)