Вася любит секреты и шифрование. Он часто пользуется шифром на основе замен и просит разработать вас функцию, которая позволит ему быстро шифровать сообщения.
Напишите функцию secret_replace
, которая принимает:
- текст требующий шифрования;
- именованные аргументы — правила замен, представляющие собой кортежи из одного или нескольких значений.
Функция должна вернуть зашифрованный текст.
Примечание
Ваше решение должно содержать только функции.
В решении не должно быть вызовов требуемых функций.
Пример
Ввод
result = secret_replace("Hello, world!", l=("hi", "y"), o=("123", "z"))
Вывод
result = 'Hehiy123, wzrhid!'
Ввод
result = secret_replace(
"ABRA-KADABRA",
A=("Z", "1", "!"),
B=("3",),
R=("X", "7"),
K=("G", "H"),
D=("0", "2"),
)
Вывод
result = 'Z3X1-G!0Z371'
Решение
Задача не сильно сложная с одной стороны. С другой стороны я не знаю ни одного решения, которое бы полностью меня устроило. Каждое из предлагаемых решений проходит проверку, но каждое из них имеет недостаток.
Первое не очень элегантно, но бережно относится к входным данным.
Второе выглядит значительно лучше, но портит входной словарь. Проблему можно было бы решить глубоким копированием словаря, но это приводит нас или к использованию внешних модулей, или к теме рекурсий, с которой нам только еще предстоит познакомиться.
Третье решение базируется на втором, но предполагает использование cycle из itertools, который мы изучили ранее.
Во всех случаях алгоритм примерно одинаков – мы читаем букву и проверяем подлежит ли она замене, если подлежит, то запоминаем (через индекс, сдвиг или внешний модуль) использованную замену и переходим к следующей букве.
Ошибка в тесте Яндекса
Задача плохо покрыта тестами и по состоянию на 29-11-2023 позволяет пройти тест коду, который зацикливается на таком вызове:
print(secret_replace('a', a=('aa', 'aaa')))
Посмотреть код
Решение
def secret_replace(text, **secrets):
new_text = ''
secrets = {key: [value, 0] for key, value in secrets.items()}
for char in text:
if char in secrets:
pos = secrets[char][1] % len(secrets[char][0])
new_text += secrets[char][0][pos]
secrets[char] = [secrets[char][0], pos + 1]
else:
new_text += char
return new_text
Решение
def secret_replace(text, **code):
new_text = ''
for char in text:
if char in code:
new_text += code[char][0]
code[char] = code[char][1:] + (code[char][0],)
else:
new_text += char
return new_text
Решение
from itertools import cycle
def secret_replace(text, **code):
new_text = ''
cycled_code = {char: cycle(replacements) for char, replacements in code.items()}
for char in text:
if char in cycled_code:
new_text += next(cycled_code[char])
else:
new_text += char
return new_text
Решение
# Пример кода с ошибкой, который проходит тесты.
def secret_replace(text, **code):
for item in code:
while item in text:
for substitution in code[item]:
if item in text:
text = text[:text.find(item)] + substitution + text[text.find(item) + 1:]
return text
def secret_replace(text, **kwargs):
text_replace = ”
for i in range(len(text)):
if text[i] in kwargs:
text_replace += kwargs[text[i]][text[:i].count(text[i]) % len(kwargs[text[i]])]
else:
text_replace += text[i]
return text_replace`
Как вариант, можно рассмотреть такое решение, которое не ломается на print(secret_replace(‘a’, a=(‘aa’, ‘aaa’)))
Спасибо за вариант! Я поправил форматирование кода.
Вариант вполне рабочий.
def secret_replace(text, **enc):
my_dict = {key: list(value) for key, value in enc.items()}
result = ”
for item in text:
if my_dict.get(item):
result += my_dict[item][0]
my_dict[item].append(my_dict[item].pop(0))
else:
result += item
return result
.pop() добавляет элегантности многим решениям. Единственный минус таких решений в том, что они очень медленные на большом объеме данных – .pop(0) очень дорогая по времени операция. По этой причине подобный код не был приведен в качестве примера.
Фактически это второе решение, где .pop() заменен такой же по смыслу операцией с помощью срезов и она тоже медленная.
Здравствуйте !
Как один из вариантов :
def secret_replace(text, **law):
s = ”
for i in range(len(text)):
s += law.get(text[i], text[i])[(text[:i].count(text[i])) % len(law.get(text[i], text[i]))]
return s
Добрый день! Спасибо, что поделились решением.
К минусам такого подхода можно отнести то, что через пару месяцев будет довольно сложно понять что делает код даже самому автору. В остальном, это правильное решение.
def secret_replace(text, **replaces):
for key in replaces:
i = 0
for _ in range(text.count(key)):
j = text.find(key)
text = text[:j] + replaces[key][i] + text[j + 1:]
i += 1
if i % len(replaces[key]) == 0:
i = 0
return text
Спасибо за предоставленное решение.
Пожалуй, главный недостаток решения заключается в том, что строка будет прогоняться столько раз, сколько ключей мы передадим. Тем не менее, такой прием вполне себе бывает оправдан.
Проходит все тесты, как мне показалось, довольно лаконичное решение
def secret_replace(text, **replaces):
new_text = ”
slv = {key: 0 for key in replaces.keys()}
for char in text:
if char in replaces:
new_text += replaces[char][(slv[char]) % len(replaces[char])]
slv[char] += 1
else:
new_text += char
return new_text
На мой взгляд, это вариант первого решения, записаный более компактно, в ущерб читабельности.
Itertools подавали же ранее, значит можно по идее пользоваться
from itertools import cycle
def secret_replace(text, **replaces):
replaces = {i: cycle(j) for i, j in replaces.items()}
new_text = ”
for i in text:
if i in replaces:
new_text += next(replaces[i])
else:
new_text += i
return new_text
Спасибо за решение. Вы используете генератор из следующей темы. И, хотя, в принципе, можно использовать все что захочется, на мой взгляд важно научиться делать такие вещи руками, чтобы подготовить мозги к более сложным алгоритмам.
Исправился
from itertools import cycle, islice
def secret_replace(text, **replaces):
replaces = {i: cycle(j) for i, j in replaces.items()}
new_text = ”
for i in text:
if i in replaces:
new_text += list(islice(replaces[i], 1))[0]
else:
new_text += i
return new_text