I want the replace a word given by the input to create a cipher, but I can’t figure out how to change each value at the same time to produce a new string.
For example the dictionary is {a: b, b: c, c: 2….} Then I want the inputted string to change each letter in the string/word to what it corresponds to in the dictionary so that a new string is generated with no letters remaining the same.
Right now this is my code:
cipher = input('Please enter the cipher text:')
import string
letters = string.ascii_lowercase
letters_list = list(letters)
cipher_list = list(cipher)
pair_letters_cipher = zip(letters_list, cipher_list)
dict_cipher = dict(pair_letters_cipher)
string_encode = input('Please enter text to encode:')
for letters, cipher in dict_cipher.items():
string_encode = string_encode.replace(letters.lower(), cipher) print(string_encode)
Replacing a specific value in a dictionary
python - String replacement with dictionaries -- follow up - Code Review Stack Exchange
Help with replace()
python - How can I use a dictionary to do multiple search-and-replace operations? - Stack Overflow
Ways of optimizing
_validate_mappingsfunction
Two listskeys = []andvalues = []are just used for accumulation and membership check, though inefficiently.
Instead, the more optimized way is to rely ondict.values()view object converted tosetfor fast containment check.
The optimized function would look as (docstrings are skipped for demo):def _validate_mappings(mappings: dict) -> None: values = set(mappings.values()) for key, value in mappings.items(): if key in values: raise _KeyValueConflictException(_KEY_VALUE_CONFLICT_EXCEPTION_MSG.format(key, value))mapping_replacefunction inuse_regex=Truemode and dealing with multiple regex replacements.
To replace a loop of numerous subsequent regex compilations and substitutions I would suggest a "single-pass" substitution powered by the following features:- regex alternation group
(...)|(...)|(...)to combine all raw patterns into one - Python
dictpreserves its insertion order since 3.7 - the respective replacement string is found using
Match.lastindexfeature (the integer index of the last matched capturing group)
Although, this trick may require non-overlapping patterns provided in
mappingsdict.
The crucialif use_regex:block:... if use_regex: keys_list = list(mappings.keys()) replacer = lambda m: mappings[keys_list[m.lastindex - 1]] pat = fr"{'|'.join(f'({k})' for k in mappings.keys())}" # composing regex alternation group replaced_string = re.sub(pat, replacer, replaced_string)- regex alternation group
I've added some extended test case (3rd one) to show how's the regex trick goes:
print(mapping_replace("simple test", {"simple": "complex", "test": "haha"}))
print(mapping_replace("124233 test", {r"\d+": "letters"}, True))
print(mapping_replace("011 test ABCbb11www", {"\d+": "symbols", "[A-Z]+": "@", "w+": "W3W"}, True))
print(mapping_replace("Hello world", {"H": "J", "J": "Y"}, False, validate_mappings=False)) # No exception!
print(mapping_replace("Hello world", {"H": "J", "J": "Y"}))
The output:
complex haha
letters test
symbols test @bbsymbolsW3W
Yello world
raise _KeyValueConflictException(_KEY_VALUE_CONFLICT_EXCEPTION_MSG.format(key, value))
__main__._KeyValueConflictException: The key of 'J' -> 'Y' conflicts with a separate mapping containing the value 'J'.
Naming
In many places, functions are described as operating on "mappings" or "set of mappings". As far as I can tell, the dictionary passed as a parameter is a mapping. Thus, the plural form can be removed in various places.
(As a disclaimer, English is not my native language - let me know if I am wrong.)
Unused value
In _validate_mapping, the list keys is populated but never used.
Limitation of the validation
The validation logic seems to be here to ensure that a substitution will not bring a pattern that will be replaced (or would have been replaced) by a different substitution.
This somehow ensure that the order of the dictionary is not important.
An example would be:
print("Yello world" == mapping_replace("Hello world", { "H": "J", "J": "Y"}, False, validate_mapping=False)) # No exception!
print("Jello world" == mapping_replace("Hello world", { "J": "H", "H": "J"}, False, validate_mapping=False)) # No exception!
However, there are various things that may be misleading with the corresponding logic under the assumption that my understanding is valid .
Handling of regexp
Regexp are not properly handled. For instance, this call:
print("\d+ test" == mapping_replace("124233 test", { r"\d+": r"\d+" }, True, validate_mapping=False))
should have the same behaviour with validate_mapping set to True or False. At the moment, it either works or throws the exception.
Opposite situation
There are situations (like above) where the exception is thrown but but probably shouldn't but there are also situations where no exception is thrown but one would be expected. This may give a feeling of "safety" which is not really valid.
An example would be:
print("YYello world" == mapping_replace("Hello world", { "H": "JJ", "J": "Y" }))
print("JJello world" == mapping_replace("Hello world", { "J": "Y", "H": "JJ" }))
Should this throw ?
My expectations
Here is the behavior I'd have expected at least for the regexp case: check that no value from the dict would be matched by any of the keys (using re.search). In reality I do not see any correct way to check for any mapping that could lead to conflicts.
address = "123 north anywhere street"
for word, initial in {"NORTH": "N", "SOUTH": "S"}.items():
address = address.replace(word.lower(), initial)
print(address)
nice and concise and readable too.
One option I don't think anyone has yet suggested is to build a regular expression containing all of the keys and then simply do one replace on the string:
>>> import re
>>> l = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}
>>> pattern = '|'.join(sorted(re.escape(k) for k in l))
>>> address = "123 north anywhere street"
>>> re.sub(pattern, lambda m: l.get(m.group(0).upper()), address, flags=re.IGNORECASE)
'123 N anywhere street'
>>>
This has the advantage that the regular expression can ignore the case of the input string without modifying it.
If you want to operate only on complete words then you can do that too with a simple modification of the pattern:
>>> pattern = r'\b({})\b'.format('|'.join(sorted(re.escape(k) for k in l)))
>>> address2 = "123 north anywhere southstreet"
>>> re.sub(pattern, lambda m: l.get(m.group(0).upper()), address2, flags=re.IGNORECASE)
'123 N anywhere southstreet'
Hi! I was wondering if there's any way to replace characters in strings that are part of a dictionary? For example:
x = {"dog" : ["corgi", "Husky", "shiba"]} ----> x = {"Dog" : ["Corgi", "Husky", "Shiba"]}
In this example, I need to capitalize the first letter in each string if it's not capitalized already. I can't use any string methods and the only list method I can use is .insert(). I can use .chr() and .ord()
If keys in dictionary have only one word is possible split, map by get and join back:
a = ' '.join(d.get(x, x) for x in string.split())
print (a)
Sam and Ann are not good friends
If possible multiple words and also is necessary use words boundaries for avoid replace substrings:
import re
string = 'John and Mary are good friends'
d = {'John and': 'Sam with', 'Mary': 'Ann', 'are good': 'are not'}
pat = '|'.join(r"\b{}\b".format(x) for x in d.keys())
a = re.sub(pat, lambda x: d.get(x.group(0)), string)
print (a)
Sam with Ann are not friends
Basic approach:
- split the long string into a list of words
- iterate through this list of words; if any word exists as a key in the given dictionary, replace that word with the corresponding value from the dictionary
- join the list of words together using space
string = 'John and Mary are good friends'
d = {'John': 'Sam', 'Mary': 'Ann', 'are': 'are not'}
s = string.split()
for i, el in enumerate(s):
if el in d:
s[i] = d[el]
print(' '.join(s))
via dict.update() function
In case you need a declarative solution, you can use dict.update() to change values in a dict.
Either like this:
my_dict.update({'key1': 'value1', 'key2': 'value2'})
or like this:
my_dict.update(key1='value1', key2='value2')
via dictionary unpacking
Since Python 3.5 you can also use dictionary unpacking for this:
my_dict = { **my_dict, 'key1': 'value1', 'key2': 'value2'}
Note: This creates a new dictionary.
via merge operator or update operator
Since Python 3.9 you can also use the merge operator on dictionaries:
my_dict = my_dict | {'key1': 'value1', 'key2': 'value2'}
Note: This creates a new dictionary.
Or you can use the update operator:
my_dict |= {'key1': 'value1', 'key2': 'value2'}
You cannot select on specific values (or types of values). You'd either make a reverse index (map numbers back to (lists of) keys) or you have to loop through all values every time.
If you are processing numbers in arbitrary order anyway, you may as well loop through all items:
for key, value in inputdict.items():
# do something with value
inputdict[key] = newvalue
otherwise I'd go with the reverse index:
from collections import defaultdict
reverse = defaultdict(list)
for key, value in inputdict.items():
reverse[value].append(key)
Now you can look up keys by value:
for key in reverse[value]:
inputdict[key] = newvalue