views:

277

answers:

9

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?

A: 

Well, I don't know ruby so I may be wrong but it seems to me the code works. It's just that for you are not taking digits into account when you do the capitalization permutations. The digit one only has one version so the capitalization looks the same. Hence: "tst1" and "tst1", "tsT1" and "tsT1" and so forth..

Have you tried the code with "acb"? Does that work fine or do you get the same problem?

Miky Dinescu
"acb" works fine - it just duplicates the results when numbers are involved...
epid
Then why not simply put a condition to not permute digits?
Miky Dinescu
A: 

A simple approach may be to remove the numbers from the string, pass the results to the function you already wrote, and then put the numbers back in at the same index.

redtuna
A: 

Maybe not the most elegant solution but you could change

puts str_arr.to_s

to

passwords << str_arr.to_s

and after the loop

puts passwords.uniq
Jonas Elfström
A: 

like this?

def perm(s)
  s2 = s.upcase
  n = s.size
  ary = [s.split(//), s2.split(//), (0..(n-1)).map{|i| 2**i}.reverse].transpose
  (0..(2**n-1)).map{|i| ary.map{|a, b, c| ((c & i) > 0) ? b : a }.join }.uniq
end

p perm('tst1')
# ["tst1", "tsT1", "tSt1", "tST1", "Tst1", "TsT1", "TSt1", "TST1"]
neoneye
Well that code is much more compact but the duplicates are still there. There should only be 8 lines of output for this string.
epid
no duplicates. 8 elements.
neoneye
Yes but it only works if the password ended with a single digit.
Jonas Elfström
a single digit.. I don't follow?
neoneye
A: 

Slight Mod to Original Program

#!/usr/local/bin/ruby
$result = []
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
    $result << str_arr.to_s
  end
  $result
end
puts permute(ARGV[0]).uniq
DigitalRoss
+1  A: 

try john the ripper, or cain and able, or any password cracking software

Nona Urbiz
What does this have to do with the question at hand?
Garrett
Well, she's solving the problem, rather than focusing on the question. Nice :-)
Felix Ogg
+1  A: 

You should make another array and instead of put just include them into the array if it isn't already included in the array. Then after your loop, join them with a \n or whatever you like.

def permute(str)

  perms = Array.new
  correct = []
  (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

    correct << str_arr.to_s unless correct.include?(str_arr.to_s)
  end

  puts correct.join("\n")
end

The Output:

>> permute("tst1")
tst1
tsT1
tSt1
tST1
Tst1
TsT1
TSt1
TST1
Garrett
Why is this being down voted, it does exactly what the person asking the question wants?
Garrett
+5  A: 

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, 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.

Felix Ogg
Biggest bonus of the Dijkstra method is that no one break/touch your code, because it will be too dense to read. You can't play around with it, you have to 'grok' it before tampering with it :-)
Felix Ogg
+1  A: 

Yet another solution (who can resist to try it?):

require 'pp'
class String
  def permute
    return [self, self.upcase].uniq if size <= 1
    [self[0..0], self[0..0].upcase].uniq.map do |x|
      self[1..-1].permute.map {|y| x+y}
    end.flatten
  end
end
pp 'tst1'.permute

returns ["tst1", "tsT1", "tSt1", "tST1", "Tst1", "TsT1", "TSt1", "TST1"]

Whoever
The return in the first line of the definition of permute could be improved to "return [self.downcase, self.upcase].uniq if size <= 1" to cover also the case that the "permuted" string has capital letters.
Whoever
And the second line of the definition of permute could be simplified to "self[0..0].permute.map do |x|" (shorter, but slightly slower).
Whoever