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