Я ломаю голову от этого: Я создаю простой генератор паролей, который использует шаблон, созданный пользователем. Шаблон содержит буквы a-f, которые обозначают 6 групп символов:
Итак, если пользователь введет ababcd, он/она получит что-то вроде abce1!
Шаблон Ababababababccd будет генерировать что-то вроде Robuxejiqu23#
.
Поскольку Ababababababccd кажется трудночитаемым, я хочу дать возможность сократить его повторениями:
Abababababccd можно было бы записать как Ab(ab){5}ccd
или даже Ab(ab){5}c{2}d
(что было бы глупо, но для полноты картины).
[ab] вступает в игру, если вы хотите иметь персонажа из группы a или группы b.
Я «построил» подходящее выражение Regex, которое фиксирует повторения в следующих комбинациях:
a{3}
, превратится в aaa
(ab){2}
будет abab
[ab]{2}
становится либо ab
, либо aa
, либо ba
и т. д.(a[bc]d){n}
Выражение регулярного выражения, которое я построил до сих пор, находит все вышеперечисленные случаи:
/(?:(\[[abcdef]+\])|\(([\[\]abcdef]+)\)|([abcdef]))\{(\d+)\}/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
стало Ababababababccd
, которое я потом обрабатываю.
Я предполагаю, что .gsub
обрабатывает каждое событие индивидуально, и я могу заменить каждое совпадение подходящим шаблоном и счетчиком. Если gsub попытается заменить их все сразу, это будет неправильный метод.
Для меня не имеет значения, какая из групп в (?:a|b|c){count} соответствует, но совпадение должно быть возвращено как $1, а количество — как $2.
Приведенное выше регулярное выражение приводит к совпадениям $1, $2, $3 и $4. 4 доллара — это всегда мой счет повторений. Но тогда мне нужно выяснить, какие $1 .. $3 не являются nil
, и использовать их для расширения. Я мог бы использовать «дешевый» корпус, а если и чехлы, то я хочу иметь элегантное решение.
В regex101 я заставил его работать (?|
, но Руби этого не понимает.
Надеюсь, понятно, чего я хочу!?
В Typescript и C# у меня все заработало, и теперь я хочу, чтобы оно работало в Ruby... :-)
Да, в каком-то смысле... В качестве вдохновения я использовал шаблон генератора паролей Keepass.
Вы уверены, что хотите создавать пароли на основе шаблонов? Кажется, это плохая идея
@Stefan Стефан, я только что отредактировал свой вопрос, надеюсь, теперь он стал более понятным. :-)
@Thomas Да, вы не ошибаетесь, но я часто создаю пароли для пользователей, которые должны быть произносимыми, и здесь вступает в силу мой шаблон. :-)
В «моей системе» я мог бы написать [abcdef]{16} и получить совершенно случайный пароль.
@engineersmnky Я соответствующим образом изменил пример, спасибо :-)
@engineersmnky Я прочитал ваше предложение, но мне, как новичку в 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
и выбираем случайный элемент.
Примечание: это не обрабатывает другие случаи, такие как:
Ab[a(bc)d]{12}
Ab(ab{5}){4}
Вау, это впечатляет! Это также сложно проглотить, но я «расшифроваю» это и соответствующим образом изменю свой код. Спасибо большое за ваши старания!!!
Хорошо, хотя часть GROUPS.then немного напоминает хвастовство ;) (без обид!), метод расширения действительно умен! Мне нравится функциональный подход. Еще раз спасибо!
@ElHippo, без обид, вы просили об элегантности, что для меня приравнивается к минимализму, где это возможно, и эта реализация немного СУШИТ. Спасибо за публикацию настоящего рубинового вопроса, я определенно предпочитаю его всей магии рельсов.
Честно говоря, вопрос родился в контексте Rails... Я понимаю ваше понимание элегантности как минимализма, но существует тонкая грань между элегантностью и загадочностью кода. Итак, для меня элегантность означает использование языка, на котором написан код, чтобы выразить как можно больше с помощью как можно меньшего количества кода (как вы сказали), сохраняя при этом простоту и читабельность. :-)
В итоге я использовал это решение:
REPETITION = /(\[[abcdef]+\]|\([\[\]abcdef]+\)|[abcdef])(?:\{(\d+)\})?/i
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]
}
password = pattern.scan(REPETITION).map do |p, c|
# Evaluate repetitions: ...{n}
s = p.start_with?('(') ? p[1..-2] : p
c ? [s] * c.to_i : [s]
end.flatten.map do |m|
# Evaluate character groups [...]
m.start_with?('[') ? m[1..-2].chars.sample : m
end.join.chars.map do |e|
# Evaluate the final pattern (repetitions and []-groups processed)
group[e][rand(group[e].length)]
end.join
@password = password
Он включает в себя магию @engineersmnky и делает отдельные шаги более понятными — по крайней мере, для меня.
«Надеюсь, понятно, чего я хочу!?» - не совсем. Вы пытаетесь создать регулярное выражение, соответствующее другим регулярным выражениям?