Regex: Ruby で一致する代替のみをキャプチャする
概要
私はこれで頭をかち割っています。 ユーザーが作成したパターンを使用する単純なパスワードジェネレーターを構築しています。パターンには、6 つの文字グループを表す文字 a ~ f が含まれています。
したがって、ユーザーが ababcd と入力すると、abce1 のような結果が得られます。 パターン Ababababababccd は Robuxejiqu23# のようなものを生成します。 「Ababababababccd」はほとんど読みにくいので、これを繰り返して省略できるようにしたいと思います。 Ababababababccd は、Ab(ab){5}ccd または Ab(ab){5}c{2}d と書くこともできます (これは愚かですが、完全を期すために)。 グループ a またはグループ b のいずれかのキャラクターを使用したい場合は、[ab] が機能します。
次の組み合わせで繰り返しをキャプチャする適切な正規表現を「構築」しました。
これまでに作成した正規表現では、上記のすべてのケースが検出されます。 /(?:([[abcdef]+])|(([[]abcdef]+))|([abcdef])){()}/i
私の作業中の Ruby コードは次のようになりますが、より洗練された解決策を見つけたいと考えています。
# class
REPETITION = /(?:(\[[abcdef]+\])|\(([\[\]abcdef]+)\)|([abcdef]))\{(\d+)\}/i
GROUPS = /\[([abcdefxyz]+)\]/i;
# method generate
pattern = params[:pattern]
group = {
'A' => params[:group_a].upcase,
'a' => params[:group_a],
'B' => params[:group_b].upcase,
'b' => params[:group_b],
'C' => params[:group_c].upcase,
'c' => params[:group_c],
'D' => params[:group_d].upcase,
'd' => params[:group_d],
'E' => params[:group_e].upcase,
'e' => params[:group_e],
'F' => params[:group_f].upcase,
'f' => params[:group_f]
}
# Evaluate repetitions: ...{n}
if pattern =~ REPETITION
pattern.gsub!(REPETITION) do
match = $1 != nil ? $1 : $2 != nil ? $2 : $3
count=$4.to_i
expanded=""
count.times do
expanded+=match
end
expanded
end
end
# Evaluate character groups [...]
if pattern =~ GROUPS
pattern.gsub!(GROUPS) do
$1[rand($1.length)]
end
end
# Evaluate the final pattern (repetitions and []-groups processed)
password=""
pattern.each_char do |c|
password+=group[c][rand(group[c].length)]
end
@password=password;
私のアイデアは、すべての出現を検索し、繰り返しを拡張された繰り返しに置き換えて、Ab(ab){5}ccd を後で処理する Abababababab Ccd にすることです。
私の仮定は、.gsub は各出現を個別に処理し、各一致を適切なパターンとカウントに置き換えることができるということです。 gsub がそれらすべてを一度に置き換えようとすると、それは間違った方法になります。
私にとって、(?:a|b|c){count} 内のどのグループが一致するかは関係ありませんが、一致は として返され、カウントは として返される必要があります。
上記の正規表現は、 、 、および に一致します。は常に私の繰り返し回数です。しかし、その後、どれが nil ではないかを見つけて、それを使用して展開する必要があります。 「安価な」ケースやケースを使用することもできますが、エレガントな解決策が必要です。
regex101 では (?| で動作するようになりましたが、Ruby はそれを理解できません。
私が何を望んでいるのかが明確だといいのですが!?
Typescript と C# では動作するようになりましたが、Ruby でも動作させたいと考えています… :-)
解決策
提供されたケースを処理するために私が思いついたものは次のとおりです。
REPETITION = /(\[[abcdef]+\]|\([\[\]abcdef]+\)|[abcdef])(?:\{(\d+)\})?/i
GROUPS = {
'a' => 'bcdfghjklmnpqrstvwxyz',
'b' => 'aeiou',
'c' => '0123456789',
'd' => '!$%&/()=?*+#_.,:;_'
}.then {|h| h.merge(h.map {|k,v| [k.upcase,v.upcase]}.to_h)}
def expand(str)
str.scan(REPETITION).map do |group, count|
sub_pattern = group.start_with?('(') ? group[/(?<=\()(.*)(?=\))/, 1] : group
count ? sub_pattern * count.to_i : sub_pattern
end.join.gsub(/\[.*?\]/) {|match| match[1..-2].chars.shuffle.first}
end
def generate(str, groups=GROUPS)
expanded = expand(str)
puts "Expansion: #{expanded}"
expanded.each_char.sum(""){|c| groups[c][rand(groups[c].length)]}
end
出力例:
patterns = ['(ab){2}','a{3}','[ab]{2}','(a[bc]d){16}','Ab(ab){5}ccd','Ab(ab){5}c{2}d']
patterns.each do |pattern|
puts "-------Pattern: #{pattern}-------"
puts "Generated: #{generate(pattern)}"
end
# -------Pattern: (ab){2}-------
# Expansion: abab
# Generated: niha
# -------Pattern: a{3}-------
# Expansion: aaa
# Generated: svh
# -------Pattern: [ab]{2}-------
# Expansion: ba
# Generated: ak
# -------Pattern: (a[bc]d){16}-------
# Expansion: abdacdabdabdabdabdabdacdacdacdacdabdabdacdabdabd
# Generated: lu+s5$cu%ye#re+mo%qu)s4?c1*h8(l5!ja*zu?g9_lu/ze!
# -------Pattern: Ab(ab){5}ccd-------
# Expansion: Ababababababccd
# Generated: Meqonicovala75!
# -------Pattern: Ab(ab){5}c{2}d-------
# Expansion: Ababababababccd
# Generated: Pezotuwegona98#
繰り返しを照合し、回数によって展開し、XOR を置き換えて完全なパターンを展開します。
次に、単純に GROUPS ハッシュから各文字を検索し、ランダムな要素を選択します。
注: これは、次のような他のケースには対応しません。