Algorithmathon Day 16: Permutations of a String
Created: 16 August 2013 Modified:Day 16’s goal has turned out to be surprisingly fascinating. Permutations of a string have a solution that is a factor of the length of the string. This means that a string with four characters will have 24 solutions which is the result of 4 x 3 x 2 x 1. This then can be our ideal goal and is represented by n!. We want to find the permutations of a string with the number of operations being equal or close to the multiplied factors of the string length.
This goal turned out to be difficult to achieve. In fact we will see that we do not achieve it. We will explore three solutions each of which provides a somewhat improved solution to the prior one. Even so we will not reach our goal. Searching for the solution was very interesting and I only stopped because I needed to move on to other subjects.
The number of iterations is the length of the string raised to the power of the number of factors in the string. A string of length 3 has 6 permutations. Which is the factorial of 3. 3 x 2 x 1 = 6. Research suggests an answer of n! x n x n.
We should now construct our RSpec tests. This time around we will leave one test failing because we never reach our ideal goal of n! iterations.
algorithm_string_spec.rb
require 'spec_helper'
shared_examples "a permutation" do | target_string, target_method, goal |
describe "When #{target_method}' is called" do
before(:each) do
@string = AlgorithmString.new(target_string)
end
it "should not return nil" do
@string.send(target_method).should_not be_nil
end
it "should return #{goal}" do
@string.send(target_method).length.should == goal
end
it "should return #{goal} when 'iteration_count' is called" do
@string.send(target_method)
@string.iteration_count.should == goal
end
it "should match @verify_array" do
verify_array = target_string.chars.to_a.permutation.map(&:join)
local_array = @string.send(target_method)
verify_array.each { | value |
local_array.include?(value).should be_true
}
end
end
end
describe AlgorithmString do
describe "Class" do
it "should have method 'permutate'" do
AlgorithmString.method_defined?(:permutate).should be_true
end
it "should have method 'permutate2'" do
AlgorithmString.method_defined?(:permutate2).should be_true
end
it "should have method 'permutate3'" do
AlgorithmString.method_defined?(:permutate3).should be_true
end
it "should have method 'iteration_count'" do
AlgorithmString.method_defined?(:iteration_count).should be_true
end
end
describe "Instance" do
describe "'ABC' permutations" do
it_should_behave_like "a permutation", "ABC","permutate", 6
it_should_behave_like "a permutation", "ABC","permutate2", 6
it_should_behave_like "a permutation", "ABC","permutate3", 6
end
describe "'ABCD' permutations" do
it_should_behave_like "a permutation", "ABCD","permutate", 24
it_should_behave_like "a permutation", "ABCD","permutate2", 24
it_should_behave_like "a permutation", "ABCD","permutate3", 24
end
describe "'ABCDE' permutations" do
it_should_behave_like "a permutation", "ABCDE","permutate", 120
it_should_behave_like "a permutation", "ABCDE","permutate2", 120
it_should_behave_like "a permutation", "ABCDE","permutate3", 120
end
end
end
Our code to meet these tests.
algorithm_string.rb
class AlgorithmString < String
attr_reader :iteration_count
#Determine all possible orderings of characters in a string
#
# - returns an array with all the possible orderings.
def permutate()
@iteration_count=0
return_array = Array.new()
permutate_by_recursion(return_array,Array.new(),Array.new(),0)
return_array
end
def permutate2()
@iteration_count=0
return_array = Array.new()
string_array = self.split('')
permutate_by_recursion2(return_array,string_array,0)
return_array
end
def permutate3()
@iteration_count=0
return_array = Array.new()
string_array = self.split('')
permutate_by_recursion3(return_array,string_array,"")
return_array
end
private
def permutate_by_recursion(return_array,build_array,tracking_array, current_position)
final_position= self.length - 1
if current_position > final_position
return_array[return_array.length]=build_array.join()
return
end
for i in 0..final_position
@iteration_count = @iteration_count + 1
unless tracking_array[i]
build_array.push(self[i])
tracking_array[i]=true
permutate_by_recursion(return_array,build_array,tracking_array,current_position + 1)
build_array.pop()
tracking_array[i]=false
end
end
end
def permutate_by_recursion2(return_array, string_array, current_position)
final_position= self.length - 1
if current_position > final_position
return_array[return_array.length]=string_array.join()
return
end
for i in current_position..final_position
@iteration_count = @iteration_count + 1
switch_character(string_array,current_position,i)
permutate_by_recursion2(return_array,string_array,current_position + 1)
switch_character(string_array,current_position,i)
end
end
def switch_character(string_array,first,second)
value_holder=string_array[second]
string_array[second]=string_array[first]
string_array[first]=value_holder
end
def permutate_by_recursion3(return_array, string_array, string_value)
if string_array.length == 1
return_array[return_array.length]=string_value + string_array[0]
return
end
string_array.each_with_index { | value, index |
@iteration_count = @iteration_count + 1
prefix = string_value + value
temp_array = string_array.clone()
temp_array.delete_at(index)
permutate_by_recursion3(return_array, temp_array, prefix)
}
end
end
As you can see the first solution permutate was not very efficient and required iterations greater than n3 and n! x n2. Our second solution permutate2 switches values around with the string and is able to do considerably better. It solves the problem somewhere between n2 and n x n2. This solutions is my favorite because it seems the least resource intensive. Finally we have permutate3 which uses the fewest iterations/recursive calls. The difference is not insignificant and the this solution could also be applied to permutate2 to reduce its number of recursive calls.
tags: Algorithm - Algorithmathon - RSpec - Ruby