So I came across an interesting problem awhile back, and I finally got around to solving it. Basically, it needs to allow the user to provide two words, and have them progressively splice together, like so:
Word 1: 123
Word 2: abc
Result: 123abc
12a3bc
1a2b3c
a1b2c3
ab1c23
abc123
The two words provided must maintain their order as they weave between eachother, and they should be arranged so that any two letters in one word are never separated by more than one letter from the other word.
I half expect to learn that I over-thought the problem, but I still think it's pretty slick. I'm open to all kinds of feedback.
EDIT: Here's the updated version. Old version is below:
!/usr/bin/ruby
class Array
def swap!(a,b)
self[a], self[b] = self[b], self[a]
self
end
end
class String
def phase(other)
if self.empty?
[other]
elsif other.empty?
[self]
else
@word1 = self.split("")
@word2 = other.split("")
@combined_words = []
@word2.each { |letter| @combined_words.push({:letter => nil, :word => nil}) }
@word1.each do |letter|
@combined_words.push({:letter => letter, :word => 1})
@combined_words.push({:letter => nil, :word => nil})
end
@word2.each { |letter| @combined_words.push({:letter => letter, :word => 2}) }
end
while @combined_words.include?({:letter => nil, :word => nil})
nil_loc = @combined_words.rindex{ |addr| addr[:word].nil? }.to_i
word2_subloc = @combined_words.drop(nil_loc).index{ |addr| addr[:word] == 2 }.to_i
if word2_subloc == 0
@combined_words.delete_at(nil_loc)
print "\n\n"
@combined_words.each do |addr|
if not addr[:letter].nil?
print addr[:letter]
end
end
print "\n\n"
else
@combined_words.swap!(nil_loc, word2_subloc + nil_loc)
end
end
end
end
puts "What is your first word?"
param1 = gets
puts "Cool, what is your second word?"
param2 = gets
puts param1.chomp.phase(param2.chomp)
Old version:
#!/usr/bin/ruby
class Array
def swap!(a,b)
self[a], self[b] = self[b], self[a]
self
end
end
class Phaser
def initialize(word1, word2)
raise unless word1.is_a?(String) && word2.is_a?(String)
@word1 = word1.split("")
@word2 = word2.split("")
@combined_words = []
@word2.each { |letter| @combined_words.push({:letter => nil, :word => nil}) }
@word1.each { |letter|
@combined_words.push({:letter => letter, :word => 1 })
@combined_words.push({:letter => nil, :word => nil})
}
@word2.each { |letter| @combined_words.push({:letter => letter, :word => 2}) }
end
def phase
if !@combined_words.include?({:letter => nil, :word => nil})
return
else
nil_loc = @combined_words.rindex{ |addr| addr[:word].nil? }.to_i
word2_subloc = @combined_words.drop(nil_loc).index{ |addr| addr[:word] == 2 }.to_i
if word2_subloc == 0
@combined_words.delete_at(nil_loc)
print "\n\n"
@combined_words.each do |addr|
if not addr[:letter].nil?
print addr[:letter]
end
end
else
@combined_words.swap!(nil_loc, word2_subloc + nil_loc)
end
phase
end
end
end
puts "What is your first word?"
param1 = gets
puts "Cool, what is your second word?"
param2 = gets
test_phase = Phaser.new(param1.chomp, param2.chomp)
test_phase.phase
1 Answer 1
Some notes:
raise unless word1.is_a?(String) && word2.is_a?(String)
: Don't lose a second testing types of arguments, it's the caller's responsability to get it right.Blank lines: You should be more careful with blank lines, used without consistency hinder readability severly. Here are my opinions on this.
I am tempted to write a bot that, upon finding an
each
,+=
,delete
,insert
,value[x] = y
or similar, automatically comments "this code looks terrible because it's in imperative style, try functional" :-) Sadly, most of the time the bot would be right. The problem is that you think about the problem in terms of how (do this, do that) instead of what, so variables are modified everywhere and it's just impossible to understand what the algorithm is doing. I've written at length about this subject, so if you're curious: FP in Ruby. Here the more natural approach seems a recursive functional algorithm.
That's how I'd write it:
class String
def interleave(other)
if self.empty?
[other]
elsif other.empty?
[self]
else
interleaved1 = [self[0]].product(self[1..-1].interleave(other))
interleaved2 = [other[0]].product(self.interleave(other[1..-1]))
(interleaved1 + interleaved2).map(&:join)
end
end
end
p "123".interleave("abc")
#=> ["123abc", "12a3bc", "12ab3c", "12abc3", "1a23bc", "1a2b3c", "1a2bc3", "1ab23c", "1ab2c3", "1abc23", "a123bc", "a12b3c", "a12bc3", "a1b23c", "a1b2c3", "a1bc23", "ab123c", "ab12c3", "ab1c23", "abc123"]
In fact I'd implement the more generic Array#interleave
(and go Array <-> String when needed). The changes in the implementation are minimal.
-
\$\begingroup\$ Thank you so much for the feedback, I'll definitely be reading those links when I get home. Just looking at your output though, your method doesn't seem to be producing the right results. For instance the 3rd element in your output is "12ab3c" but it should be "1a2b3c", which in your output doesn't occur until the 6th element \$\endgroup\$bad_sample– bad_sample2013年07月15日 16:37:34 +00:00Commented Jul 15, 2013 at 16:37
-
\$\begingroup\$ @bad_sample: I noticed your output is shorter, I create all possible interleavings. Can you add a link to the specifications on the question? \$\endgroup\$tokland– tokland2013年07月15日 17:51:04 +00:00Commented Jul 15, 2013 at 17:51
-
\$\begingroup\$ No link, it's just my own idea. I'll update my post to be more clear about the requireemnts \$\endgroup\$bad_sample– bad_sample2013年07月16日 18:01:21 +00:00Commented Jul 16, 2013 at 18:01
-
\$\begingroup\$ I made some changes and uploaded the edited version based on some of your feedback. It's still imperative style but I did implement some of your changes \$\endgroup\$bad_sample– bad_sample2013年07月20日 20:18:51 +00:00Commented Jul 20, 2013 at 20:18
-
\$\begingroup\$ @bad_sample: What I don't understand are the specifications of the problem: which outputs do you want and which don't. For example "1ab23c" seems a valid interleaving of 123 and abc, why isn't in your output? \$\endgroup\$tokland– tokland2013年07月20日 23:05:13 +00:00Commented Jul 20, 2013 at 23:05