Capitalization Permutations

capitalization, permutation, ruby

Solution

What a great opportunity to put my classes of "Derivation of algorithms", the Dijkstra method, back from the days of University into practice here. This is the 'clean' version

require 'set'

def generate_perms(str)
  if str.length == 1
    return Set.new([str.downcase, str.upcase])
  else 
    head = str[0..0]
    tail = str[1..-1]
    perms_sub = generate_perms(tail)
    d = Set.new(perms_sub.collect{|p| head.downcase + p})
    u = Set.new(perms_sub.collect{|p| head.upcase + p})
    return d | u 
  end  
end

EDIT: Dijkstra taught us not to optimize prematurely, so I figured the Array-version would better be added separately :-) :

def perms(str)
  if str.length == 1
    return Array.new([str.downcase, str.upcase])
  else 
    head = str[0..0]
    tail = str[1..-1]
    perms_sub = perms(tail)
    d = perms_sub.collect{|p| head.downcase + p}
    u = perms_sub.collect{|p| head.upcase + p}
    return (d +  u).uniq 
  end  
end

And to make it blazing fast one converts into tail recursion, with the help of an extra method argument:

# tail recursion version, no stack-overflows :-) 
def perms_tail(str, perms)
  if str.length == 1
    return perms.collect{|p| [p + str.upcase, p+ str.downcase]}.flatten.uniq
  else
    tail = perms.collect{|p| [p + str[0..0].upcase, p+ str[0..0].downcase]}.flatten
    perms_tail(str[1..-1], tail)
  end

end

begin
  perms_tail("tst1",[""]).each{|p| puts p}
end

Now this is actually very slow, but tail recursion allows for a simple re-write (see for yourself) into the optimized version:

def perms_fast_tail(str)
  perms = [""]
  tail = str.downcase
  while tail.length > 0 do
    head, tail, psize = tail[0..0], tail[1..-1], perms.size
    hu = head.upcase
    for i in (0...psize)
      tp = perms[i] 
      perms[i] = tp + hu
      if hu != head
        perms.push(tp + head)
      end  
    end  
  end
  perms
end 

How much does this matter? Well let's run some timed tests, for the fun of it:

begin
  str = "Thequickbrownfox"
  start = Time.now
  perms_tail(str,[""])
  puts "tail: #{Time.now - start}"

  start2 = Time.now
  perms(str)
  puts "normal: #{Time.now - start2}"

  start3 = Time.now
  perms_fast_tail(str)
  puts "superfast: #{Time.now - start3}"
end

On my machine this shows the difference:

tail: 0.982241
normal: 0.285104
superfast: 0.168895

The speed increase and performance benefits become visible from non-trivial strings; "tst1" will run fast in the clean version. Hence, Dijkstra was right: no need to optimize. Though it was just fun to do it anyway.

Problem

I wanted to write a snippet of ruby that would take a string and output all possible permutations of capitalizations. Basically, I have a password that I remember, but I don't remember how it is capitalized. I have the following so far: ``` def permute(str) perms = Array.new (2 ** str.size).times { perms << str } perms.each_index do |i| binary = i.to_s(2) str_arr = perms[i].split(//) bin_arr = binary.split(//) while ( bin_arr.size < str_arr.size ) bin_arr.unshift('0') end bin_arr.each_index do |b| str_arr[b].upcase! if bin_arr[b] == '1' end puts str_arr.to_s end end ``` This works well enough, but I was wondering if any rubyists out there could help me refine it so that it doesn't have to work needlessly on strings with numbers. For example, the string "tst1" generates: ``` tst1 tst1 tsT1 tsT1 tSt1 tSt1 tST1 tST1 Tst1 Tst1 TsT1 TsT1 TSt1 TSt1 TST1 TST1 ``` The output I'm looking for is: ``` tst1 tsT1 tSt1 tST1 Tst1 TsT1 TSt1 TST1 ``` Any ideas?

Original source