Saari Development

This blog is intended to be a log of my (Ali Rizvi's) professional ramblings as a software development engineer. I intend to add logs of my experience with different technologies, software, tech books/articles and related stuff from time to time. My intention is to have an archive for my personal use and public benefit.

Friday, January 04, 2008

Ruby: Oblongular Number Spirals Solution


1 # Oblongular Number Spirals
2 # http://codegolf.com/oblongular-number-spirals
3
4 class Spiral
5 attr_accessor :row_num, :col_num, :direction
6
7 def initialize(m, n)
8 @spiral = []
9 @value = 0
10 @row_num = m
11 @col_num = n
12 @direction = :right
13 end
14
15 def get(row, col)
16 if (row < 0 || row >= row_num || col < 0 || col >= col_num)
17 raise RangeError.new("Out of Bound : row = #{row} col = #{col}")
18 end
19 @spiral[row] ||= Array.new
20 @spiral[row][col] ||= nil
21 @spiral[row][col]
22 end
23
24 def increment_fill(row, col)
25 @value += 1
26 fill(row, col, @value)
27 end
28
29 def fill(row, col, value)
30 if (row < 0 || row >= row_num || col < 0 || col >= col_num)
31 raise RangeError.new("Out of Bound : row = #{row} col = #{col}")
32 end
33 @spiral[row] ||= Array.new
34 @spiral[row][col] = value
35 end
36
37 def populate(x = 0, y = 0)
38 count = 0
39 while (1)
40 increment_fill(x, y)
41 count += 1
42 # exit the loop once iteration == total_cells
43 break if count == row_num * col_num
44 x, y = next_cell(x, y)
45 end
46 @spiral
47 end
48
49 def next_cell(row, col)
50 new_row = row
51 new_col = col
52
53 case @direction
54 when :right
55 new_col = col + 1
56 when :left
57 new_col = col - 1
58 when :down
59 new_row = row + 1
60 when :up
61 new_row = row - 1
62 end
63
64 if ((new_col >= col_num || new_col < 0))
65 change_direction
66 next_cell(row, col)
67 elsif ((new_row >= row_num || new_row < 0))
68 change_direction
69 next_cell(row, col)
70 elsif (get(new_row, new_col) != nil)
71 change_direction
72 next_cell(row, col)
73 else
74 return [new_row, new_col]
75 end
76 end
77
78 def change_direction
79 case @direction
80 when :right
81 @direction = :down
82 when :down
83 @direction = :left
84 when :left
85 @direction = :up
86 when :up
87 @direction = :right
88 else
89 raise ArgumentError.new("Illegal value for current_direction #{@direction}")
90 end
91 end
92
93 def dump
94 display = ''
95 @spiral.each do |row|
96 row.each do |col|
97 display << sprintf("%3d ", col)
98 end
99 display << "\n"
100 end
101 puts display
102 end
103 end
104
105 if $0 == __FILE__
106 s = Spiral.new(ARGV[1].to_i, ARGV[0].to_i)
107 s.populate
108 s.dump
109 end
110


1 $:.unshift File.join(File.dirname(__FILE__),'..','lib')
2
3 require 'test/unit'
4 require 'spiral'
5
6 class TestSpiral < Test::Unit::TestCase
7 def setup
8 @spiral = Spiral.new(4, 5)
9 end
10
11 def test_increment_fill_0_0_first
12 @spiral.increment_fill(0, 0)
13 assert_equal(1, @spiral.get(0, 0))
14 end
15
16 def test_increment_fill_1_1_first
17 @spiral.increment_fill(1, 1)
18 assert_equal(1, @spiral.get(1, 1))
19 end
20
21 def test_increment_fill_successive
22 @spiral.increment_fill(0,0)
23 @spiral.increment_fill(0,1)
24 @spiral.increment_fill(1,1)
25 assert_equal(3, @spiral.get(1, 1))
26 end
27
28 def test_get_beyond_limit_raises_exception
29 assert_raise(RangeError) {
30 @spiral.get(@spiral.row_num + 1, @spiral.col_num + 1)
31 }
32 end
33
34 def test_get_below_zero_raises_exception
35 assert_raise(RangeError) {
36 @spiral.get(-1, 0)
37 }
38 end
39
40 def test_get_at_limit_raises_exception
41 assert_raise(RangeError) {
42 @spiral.get(@spiral.row_num, @spiral.col_num)
43 }
44 end
45
46
47 def test_unfill_get_with_in_range_does_not_raise_exception
48 assert_nothing_raised() {
49 @spiral.get(@spiral.row_num - 1, @spiral.col_num - 1)
50 }
51 end
52
53 def test_unfill_get_with_in_range_returns_nil
54 assert_equal(nil, @spiral.get(@spiral.row_num - 1, @spiral.col_num - 1))
55 end
56
57 def test_fill_beyond_range_raises_exception
58 assert_raise(RangeError) {
59 @spiral.increment_fill(@spiral.row_num + 1, @spiral.col_num + 1)
60 }
61 end
62
63 def test_fill_beyond_range_raises_exception
64 assert_raise(RangeError) {
65 @spiral.increment_fill(0, -1)
66 }
67 end
68
69 def test_fill_at_range_raises_exception
70 assert_raise(RangeError) {
71 @spiral.increment_fill(@spiral.row_num, @spiral.col_num)
72 }
73 end
74
75 def test_populate
76 spiral = [
77 [1, 2, 3, 4, 5],
78 [14, 15, 16, 17, 6],
79 [13, 20, 19, 18, 7],
80 [12, 11, 10, 9, 8]
81 ]
82 s = @spiral.populate
83 assert_equal(spiral, s)
84 end
85
86 def test_change_directon_right
87 @spiral.direction = :right
88 @spiral.change_direction
89 assert_equal(:down, @spiral.direction)
90 end
91
92 def test_change_directon_up
93 @spiral.direction = :up
94 @spiral.change_direction
95 assert_equal(:right, @spiral.direction)
96 end
97
98 def test_next_cell_0_0
99 x, y = @spiral.next_cell(0,0)
100 assert_equal(0, x)
101 assert_equal(1, y)
102 end
103
104 def test_next_cell_0_4
105 x, y = @spiral.next_cell(0,4)
106 assert_equal(1, x)
107 assert_equal(4, y)
108 end
109
110 end
111

Wednesday, January 02, 2008

Ruby : Time Math Interview Problem With Bug Fixed (still writing test first)

While discussing my friend Arsalan's C# (seemlessly compiled on my linux machine using mcs)
solution and testing it out I found a bug in my own code that. The problem was when adding
more than 12 hours (> 720 minutes) it was not doing the right thing. The code can still be
refactored for cleaner solution but it is too late at night to do that now. Also, my wife
gave me another idea to convert the time to minutes before adding which I will try out later.

1 # Without using any built in date or time functions, write a function or method
2 # that accepts two mandatory arguments. The first argument is a string of the
3 # format "[H]H:MM {AM|PM}" and the second argument is an integer. Assume the
4 # integer is the number of minutes to add to the string. The return value of
5 # the function should be a string of the same format as the first argument.
6 # For example AddMinutes("9:13 AM", 10) would return "9:23 AM". The exercise
7 # isn't meant to be too hard. I just want to see how you code. Feel free to
8 # do it procedurally or in an object oriented way, whichever you prefer. Use
9 # any language you want. Write production quality code.
10 # Question Source: http://blist.com/blog/
11
12 # the following solution was developed using TDD
13
14 require 'test/unit'
15
16 class TestTimeCalc < Test::Unit::TestCase
17
18 def setup
19 @time = "9:13 AM"
20 end
21
22 def test_new_time_cal
23 assert_not_nil(TimeCalc.new)
24 end
25
26 def test_add_minute_zero
27 assert_equal(@time, TimeCalc.add_minutes(@time, 0))
28 end
29
30 def test_add_minute_ten
31 assert_equal("9:23 AM", TimeCalc.add_minutes(@time, 10))
32 end
33
34 def test_add_minute_thirteen
35 assert_equal("9:26 AM", TimeCalc.add_minutes(@time, 13))
36 end
37
38 def test_add_hour
39 assert_equal("10:13 AM", TimeCalc.add_minutes(@time, 60))
40 end
41
42 def test_add_two_hours_fifteen_minutes
43 assert_equal("11:28 AM", TimeCalc.add_minutes(@time, 135))
44 end
45
46 def test_add_past_meridiem
47 # 785 minutes = 13 hours and 5 minutes
48 assert_equal("10:18 PM", TimeCalc.add_minutes(@time, 785))
49 end
50
51 def test_alpha_hour_min_format_throws_exception
52 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD AM", 10) }
53 end
54
55 def test_bad_meridiem_throws_exception
56 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD TM", 10) }
57 end
58
59 def test_hr_greater_than_twelve
60 assert_raise(ArgumentError) { TimeCalc.add_minutes("13:00 PM", 10) }
61 end
62
63 def test_min_greater_than_fifty_nine
64 assert_raise(ArgumentError) { TimeCalc.add_minutes("12:60 PM", 10) }
65 end
66
67 def test_add_lot_of_minutes
68 # 1000 minutes = 16 hours and 40 minutes
69 assert_equal("1:53 AM", TimeCalc.add_minutes(@time, 1000))
70 end
71
72 def test_add_up_to_noon
73 # 9:13 AM plus 2 hr 47 min (107)
74 assert_equal("12:00 PM", TimeCalc.add_minutes(@time, 167))
75 end
76
77 def test_add_whole_lot_of_minutes
78 # 9999 minutes = 166 hours and 39 minutes
79 assert_equal("7:52 AM", TimeCalc.add_minutes(@time, 9999))
80 end
81
82 end # end class TestTimeCalc
83
84 class TimeCalc
85
86 def self.add_minutes(time, minutes)
87 (hour, min, meridiem) = parse_time_string(time)
88
89 hour_increment = (min + minutes)/60
90 min_increment = (min + minutes)%60 - min
91
92 while (hour + hour_increment > 12)
93 meridiem = (meridiem == 'AM' ? 'PM' : 'AM')
94 hour_increment -= 12
95 end
96
97 hour += hour_increment
98 # special case for 12th hour
99 meridiem = (meridiem == 'AM' ? 'PM' : 'AM') if hour == 12
100 min += min_increment
101
102 hour.to_s + ":" + sprintf('%02d', min) + " " + meridiem
103 end
104
105 private
106
107 def self.parse_time_string(time)
108 raise ArgumentError unless (matches = time.match(/(\d{1,2}):(\d{1,2})\s+(\w{2})/))
109 matches = time.match(/^(\d{1,2}):(\d{1,2})\s+([A|P]M)$/)
110 hour = matches[1].to_i
111 min = matches[2].to_i
112 meridiem = matches[3]
113 raise ArgumentError unless (hour <= 12)
114 raise ArgumentError unless (min < 60)
115 return [hour, min, meridiem]
116 end
117 end # end class TimeCalc
118
119 if __FILE__ == $0
120 puts TimeCalc.add_minutes(ARGV[0], ARGV[1].to_i)
121 end
122

# vim colorscheme = delek

Sunday, December 30, 2007

Ruby: One liner to strip the line number from code posted

In response to my previous post my friend Arsalan posted his C# solution on my shared friend blog SaarayDost

Both my solution and his had line numbers with the code which makes it easy to reference a line number in a discussion but if you want to run and test code it can be painful to strip them out one by one.

I wrote the following quick ruby one liner that can be run on a file after copy-pasting the code and saving it.

ruby -lne 'puts $_.gsub(/^\s?\d+:?/, "")' ali.rb > ali.rb

I am sure there good be a better more clever way to do this after all TIMTOWDI but here I use the l to remove newline at the end of each line, n to iterate over the given file one line at a time and e to execute the following code string (in quotes, on each line). $_ is the magic variable (a perl legacy in ruby) that magically populated, in this case with the current line of file being processed. gsub simply removed the pattern match in the first argument by the string in the second argument.

While searching for a good description of $_ I also found this interesting link with other useful ruby one-liners.

Update: found another way to achieve the above result. -p option prints the $_ at the end of each iteration.

ruby -lpe '$_.gsub!(/^\s?\d+:?/, "")' ali.rb > ali.rb

Friday, December 28, 2007

Ruby : Time Math Interview Problem Done Test First

Question from : Blist.com Career Page

1 # Without using any built in date or time functions, write a function or method
2 # that accepts two mandatory arguments. The first argument is a string of the
3 # format "[H]H:MM {AM|PM}" and the second argument is an integer. Assume the
4 # integer is the number of minutes to add to the string. The return value of
5 # the function should be a string of the same format as the first argument.
6 # For example AddMinutes("9:13 AM", 10) would return "9:23 AM". The exercise
7 # isn't meant to be too hard. I just want to see how you code. Feel free to
8 # do it procedurally or in an object oriented way, whichever you prefer. Use
9 # any language you want. Write production quality code.
10 # Question Source: http://blist.com/blog/
11
12 # the following solution was developed using TDD
13
14 require 'test/unit'
15
16 class TestTimeCalc < Test::Unit::TestCase
17
18 def setup
19 @time = "9:13 AM"
20 end
21
22 def test_new_time_cal
23 assert_not_nil(TimeCalc.new)
24 end
25
26 def test_add_minute_zero
27 assert_equal(@time, TimeCalc.add_minutes(@time, 0))
28 end
29
30 def test_add_minute_ten
31 assert_equal("9:23 AM", TimeCalc.add_minutes(@time, 10))
32 end
33
34 def test_add_minute_thirteen
35 assert_equal("9:26 AM", TimeCalc.add_minutes(@time, 13))
36 end
37
38 def test_add_hour
39 assert_equal("10:13 AM", TimeCalc.add_minutes(@time, 60))
40 end
41
42 def test_add_two_hours_fifteen_minutes
43 assert_equal("11:28 AM", TimeCalc.add_minutes(@time, 135))
44 end
45
46 def test_add_past_meridiem
47 # 785 minutes = 13 hours and 5 minutes
48 assert_equal("10:18 PM", TimeCalc.add_minutes(@time, 785))
49 end
50
51 def test_alpha_hour_min_format_throws_exception
52 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD AM", 10) }
53 end
54
55 def test_bad_meridiem_throws_exception
56 assert_raise(ArgumentError) { TimeCalc.add_minutes("AB:CD TM", 10) }
57 end
58
59 def test_hr_greater_than_twelve
60 assert_raise(ArgumentError) { TimeCalc.add_minutes("13:00 PM", 10) }
61 end
62
63 def test_min_greater_than_fifty_nine
64 assert_raise(ArgumentError) { TimeCalc.add_minutes("12:60 PM", 10) }
65 end
66
67 end # end class TestTimeCalc
68
69 class TimeCalc
70
71 def self.add_minutes(time, minutes)
72 (hour, min, meridiem) = parse_time_string(time)
73
74 hour_increment = (min + minutes)/60
75 min_increment = (min + minutes)%60 - min
76 if (hour_increment >= 12)
77 meridiem = (meridiem == 'AM' ? 'PM' : 'AM')
78 hour_increment -= 12
79 end
80
81 hour += hour_increment
82 min += min_increment
83
84 hour.to_s + ":" + min.to_s + " " + meridiem
85 end
86
87 private
88
89 def self.parse_time_string(time)
90 raise ArgumentError unless (matches = time.match(/(\d{1,2}):(\d{1,2})\s+(\w{2})/))
91 matches = time.match(/^(\d{1,2}):(\d{1,2})\s+([A|P]M)$/)
92 hour = matches[1].to_i
93 min = matches[2].to_i
94 meridiem = matches[3]
95 raise ArgumentError unless (hour <= 12)
96 raise ArgumentError unless (min < 60)
97 return [hour, min, meridiem]
98 end
99
100 end # end class TimeCalc

Sunday, December 23, 2007

Vim: Inserting output of unix/linux command

I had seen other vim experts do this but couldn't remember how they did. Today I finally search and found how to insert the output of a unix/linux command directly in a vim buffer(without copy-pasting).

:r!ls /home/

or in the visual mode (by pressing V) simply !ls /home/

Simple isn't it and quite useful at times.

Tuesday, October 02, 2007

Ruby: net/smtp weirdness - to address can't contain dash/hyphen (-)

This caused me a lot of grief and debugging time.

I am using code like this to send emails from a CGI script:

Net::SMTP.start('smtp.example.com', 25) do |smtp|
smtp.open_message_stream('from@example.com', ['dest-address@example.com']) do |f|
f.puts 'From: from@example.com'
f.puts 'To: dest-address@example.com'
f.puts 'Cc: cc-address@example.com'
f.puts 'Subject: test message'
f.puts
f.puts 'This is a test message.'
end
end


and this is failing silent in my case.

I have discovered through trial and error that if I remove the -(dash) from the to address it works. Dashes in from or CC addresses does not matter.

Tuesday, September 25, 2007

Vim: Delete every line in the file that does not match a pattern

I realized the power of :g much later than many other commands in vim and I think this one has been there even in vi for a long time.

Here is a great tip that covers the virtues of :g in vim:

Tip #227: Power of :g

The reason I ended up with this tip was that I needed to find a simple command that would delete all lines matching a pattern.

I knew how to delete all lines that matched a pattern.

:g/pattern/d

but I was looking for the inverse and I found it :g's cousin :v in the above tip as a user comment.

:v/patter/d

did the job very well. Once again vim saved the day. :-)

Wednesday, September 12, 2007

*nix: nslookup to resolve an IP to host name

nslookup is useful to resolve an ip address to a host name in the linux/unix environment.

nslookup - query Internet name servers interactively

Wednesday, August 08, 2007

Ruby : Where are the gems on ubuntu 7.04

It took me some time to figure it out where the gems are installed on my ubuntu 7.04 linux machine so I thought I would save it here as a reminder. The ruby gems are stored here:

/var/lib/gems/1.8/gems


What I did to find this is to run gem install without sudo to see the error message:

me@desktop:/usr/lib/ruby/1.8$ gem install rails
Bulk updating Gem source index for: http://gems.rubyforge.org
ERROR: While executing gem ... (Errno::EACCES)
Permission denied - /var/lib/gems/1.8/cache/rails-1.2.3.gem


The permission denied message gave me my answer. Who said error messages are not educational?

Hurray!

Sunday, August 05, 2007

MySQL and Rails: mysql.sock not found

I started doing my webapp on my brand new server (ubuntu linux 7.04) and when trying to run my first migration I got the following error message:

rake db:migrate
(in /home/rizvi/projects/somewebapp)
rake aborted!
No such file or directory - /tmp/mysql.sock

Here is a useful mysql command to find out mysql defaults:

mysqladmin --print-defaults
mysqladmin would have been started with the following arguments:
--port=3306 --socket=/var/run/mysqld/mysqld.sock

Here is what solved the problem:

sudo apt-get install libmysql-ruby


Note: The above is an alternative to :
sudo gem install mysql


Now I get:

rake db:migrate
(in /home/rizvi/projects/somewebapp)
rake aborted!
Unknown database 'somewebapp_development'
(See full trace by running task with --trace)

So I need to do the following:

mysqladmin -u root create somewebapp_development -p
mysqladmin -u root create somewebapp_test -p
mysqladmin -u root create somewebapp_production -p

Reference: RailsOnUbuntu