Write the program three times.
- The first time, make program as readable as you can make it.
- The second time, optimize the program to run fast fast as you can make it.
- The third time, write as extendible a program as you can.
Even that, though, leaves a lot of room for interpretation. For instance, "readable" to whom? I decided to divide this into two audiences. The first is people entirely new to the language (in this case, Ruby) and possibly to programming in general. Ruby has quite an advantage here, being so expressive and easy to pick up. The second is people who do know the language, including common idioms, and presumably programming in general.
Since the two are so radically different, at least the way I did them, I'll give you them both at once. First the one for complete newbies:
#! /usr/bin/ruby
# Dave Thomas Code Kata #8: Conflicting Objectives
# See http://codekata.pragprog.com/2007/01/kata_eight_conf.html
# Solution by Dave Aronson
# version 1: make it readable
# part A: make it readable to a ruby newbie, possibly non-programmer
def make_word_lists file_name
word_lists = []
for len in 1 .. 6 do
word_lists[len] = []
end
puts "Reading #{file_name}..."
dict = File.new file_name, 'r'
for line in dict do
word = line.chomp
if word.length == 1 then word_lists[1] << word
elsif word.length == 2 then word_lists[2] << word
elsif word.length == 3 then word_lists[3] << word
elsif word.length == 4 then word_lists[4] << word
elsif word.length == 5 then word_lists[5] << word
elsif word.length == 6 then word_lists[6] << word
end
end
dict.close
return word_lists
end
def find_combinations word_lists
puts 'Finding combinations....'
combinations = 0
for word in word_lists[6] do
part_1 = word[0 .. 0]
part_2 = word[1 .. 5]
if word_lists[1].include? part_1 and word_lists[5].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations = combinations + 1
end
part_1 = word[0 .. 1]
part_2 = word[2 .. 5]
if word_lists[2].include? part_1 and word_lists[4].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations = combinations + 1
end
part_1 = word[0 .. 2]
part_2 = word[3 .. 5]
if word_lists[3].include? part_1 and word_lists[3].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations = combinations + 1
end
part_1 = word[0 .. 3]
part_2 = word[4 .. 5]
if word_lists[4].include? part_1 and word_lists[2].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations = combinations + 1
end
part_1 = word[0 .. 4]
part_2 = word[5 .. 5]
if word_lists[5].include? part_1 and word_lists[1].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations = combinations + 1
end
end
return combinations
end
start_time = Time.now
word_lists = make_word_lists ARGV[0]
combinations = find_combinations word_lists
puts "We found a total of #{combinations} combinations"
end_time = Time.now
run_time = end_time - start_time
puts "Running time: #{run_time} seconds"Yes, it's incredibly slow, due to use of simple arrays rather than hashes, since Ruby-newbies, or even more so, programming-newbies, would not be familiar with hashes or other such semi-advanced data structures. I could make it faster by using a binary search function, but that would also have to be in there and count against the readability! Besides, speed is for Part 2. And now the one for Ruby programmers (who are presumably familiar with hashes):
#! /usr/bin/ruby
# Dave Thomas Code Kata #8: Conflicting Objectives
# See http://codekata.pragprog.com/2007/01/kata_eight_conf.html
# Solution by Dave Aronson
# version 1: make it readable
# part B: make it readable to someone who knows Ruby
def make_word_lists file_name
word_lists = []
for len in 1 .. 6 do word_lists[len] = {} end
dict = File.open file_name, 'r'
dict.each_line do |line|
word = line.chomp
len = word.length
word_lists[len][word] = nil if len <= 6
end
dict.close
word_lists
end
def find_combinations word_lists
combinations = 0
for word in word_lists[6].keys.sort do
for first_part_len in 1 .. 5 do
part_1 = word[0 .. first_part_len - 1]
part_2 = word[first_part_len .. 5]
if word_lists[first_part_len].include? part_1 and
word_lists[6-first_part_len].include? part_2
puts "#{part_1} + #{part_2} = #{word}"
combinations += 1
end
end
end
combinations
end
start_time = Time.now
word_lists = make_word_lists ARGV[0]
combinations = find_combinations word_lists
puts "We found a total of #{combinations} combinations"
puts "Running time: #{Time.now - start_time} seconds"So, dear readers, let's hear your input. Did you find any of that poorly readable? What do you think would have been more readable? Should I have coded up a binary search, to make Part A tolerably fast? Or going the other way, do you think I went overboard? (Especially in the horribly verbose Part A, with the inner loop unrolled?)
Of course, already I see some ways I could have made these clearer. I could have used File.open instead of .new. I seem to recall some web page saying I could have used 'read' instead of just 'r'.
ReplyDeleteAnd in case you were wondering, Part 2 is coming next week, and Part 3 the week after that.