8

文字列を受け取り、可能なすべての大文字と小文字の組み合わせを出力する Ruby のスニペットを書きたかったのです。基本的に、私は覚えているパスワードを持っていますが、大文字の使い方を覚えていません。

これまでのところ、次のものがあります。

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

これは十分に機能しますが、数字を含む文字列で不必要に機能する必要がないように、ルビイストがそれを改良するのを手伝ってくれるかどうか疑問に思っていました.

たとえば、文字列「tst1」は次を生成します。

tst1
tst1
tsT1
tsT1
tSt1
tSt1
tST1
tST1
Tst1
Tst1
TsT1
TsT1
TSt1
TSt1
TST1
TST1

私が探している出力は次のとおりです。

tst1
tsT1
tSt1
tST1
Tst1
TsT1
TSt1
TST1

何か案は?

4

9 に答える 9

13

大学時代の「アルゴリズムの導出」、ダイクストラ法をここで実践する絶好の機会です。これは「クリーン」バージョンです

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

編集:ダイクストラは時期尚早に最適化しないように教えてくれたので、配列バージョンを個別に追加する方がよいと考えました:-):

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

そして、超高速にするために、追加のメソッド引数を使用して末尾再帰に変換します。

# 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

これは実際には非常に遅いですが、末尾再帰により、最適化されたバージョンに簡単に書き直すことができます (自分の目で確かめてください) 。

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 

これはどのくらい重要ですか?楽しみのために、いくつかの時限テストを実行しましょう。

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

私のマシンでは、これは違いを示しています:

tail: 0.982241
normal: 0.285104
superfast: 0.168895

速度の向上とパフォーマンスの利点は、重要な文字列から明らかになります。「tst1」は、クリーン バージョンで高速に実行されます。したがって、ダイクストラは正しかった: 最適化する必要はありません。とにかくそれをするのはただ楽しかったです。

于 2009-09-09T10:46:11.020 に答える
2

ジョン・ザ・リッパー、カイン・アンド・アブル、またはパスワード解読ソフトウェアを試してください

于 2009-09-07T17:00:20.727 に答える
2

put配列にまだ含まれていない場合は、配列に含めるだけでなく、別の配列を作成する必要があります。次に、ループの後に、\nまたは好きなものでそれらを結合します。

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

出力:

>> permute("tst1")
tst1
tsT1
tSt1
tST1
Tst1
TsT1
TSt1
TST1
于 2009-09-07T17:03:57.740 に答える
1

さらに別の解決策 (誰が試したくないですか?):

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

["tst1"、"tsT1"、"tSt1"、"tST1"、"Tst1"、"TsT1"、"TSt1"、"TST1"] を返します。

于 2009-09-09T12:21:50.520 に答える
0

まあ、私はルビーを知らないので間違っているかもしれませんが、コードは動作しているようです。大文字の順列を行うときに数字を考慮していないというだけです。数字の 1 には 1 つのバージョンしかないため、大文字と小文字は同じに見えます。したがって、「tst1」と「tst1」、「tsT1」と「tsT1」など..

「acb」でコードを試しましたか?それはうまくいきますか、それとも同じ問題が発生しますか?

于 2009-09-07T16:47:21.110 に答える
0

簡単な方法は、文字列から数値を削除し、結果を既に記述した関数に渡し、数値を同じインデックスに戻すことです。

于 2009-09-07T16:48:12.417 に答える
0

おそらく最もエレガントなソリューションではないかもしれませんが、変更できます

puts str_arr.to_s

passwords << str_arr.to_s

そしてループの後

puts passwords.uniq
于 2009-09-07T16:53:02.657 に答える
0

このような?

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"]
于 2009-09-07T16:53:55.217 に答える
0

元のプログラムへのわずかな変更

#!/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
于 2009-09-07T16:59:09.853 に答える