similar function to php's str_replace in python? - python

is there a similar function in python that takes search(array) and replace(array) as a parameter? Then takes a value from each array and uses them to do search and replace on subject(string).
I know I can achieve this using for loops, but just looking more elegant way.

I believe the answer is no.
I would specify your search/replace strings in a list, and the iterate over it:
edits = [(search0, replace0), (search1, replace1), (search2, replace2)] # etc.
for search, replace in edits:
s = s.replace(search, replace)
Even if python did have a str_replace-style function, I think I would still separate out my search/replace strings as a list, so really this is only taking one extra line of code.
Finally, this is a programming language after all. If it doesn't supply the function you want, you can always define it yourself.

Heh - you could use the one-liner below whose elegance is second only to its convenience :-P
(Acts like PHP when search is longer than replace, too, if I read that correctly in the PHP docs.):
**** Edit: This new version works for all sized substrings to replace. ****
>>> subject = "Coming up with these convoluted things can be very addictive."
>>> search = ['Coming', 'with', 'things', 'addictive.', ' up', ' these', 'convoluted ', ' very']
>>> replace = ['Making', 'Python', 'one-liners', 'fun!']
>>> reduce(lambda s, p: s.replace(p[0],p[1]),[subject]+zip(search, replace+['']*(len(search)-len(replace))))
'Making Python one-liners can be fun!'

Do it with regexps:
import re
def replace_from_list(replacements, str):
def escape_string_to_regex(str):
return re.sub(r"([\\.^$*+?{}[\]|\(\)])", r"\\\1", str)
def get_replacement(match):
return replacements[match.group(0)]
replacements = dict(replacements)
replace_from = [escape_string_to_regex(r) for r in replacements.keys()]
regex = "|".join(["(%s)" % r for r in replace_from])
repl = re.compile(regex)
return repl.sub(get_replacement, str)
# Simple replacement:
assert replace_from_list([("in1", "out1")], "in1") == "out1"
# Replacements are never themselves replaced, even if later search strings match
# earlier destination strings:
assert replace_from_list([("1", "2"), ("2", "3")], "123") == "233"
# These are plain strings, not regexps:
assert replace_from_list([("...", "out")], "abc ...") == "abc out"
Using regexps for this makes the searching fast. This won't iteratively replace replacements with further replacements, which is usually what's wanted.

Made a tiny recursive function for this
def str_replace(sbjct, srch, rplc):
if len(sbjct) == 0:
return ''
if len(srch) == 1:
return sbjct.replace(srch[0], rplc[0])
lst = sbjct.split(srch[0])
reslst = []
for s in lst:
reslst.append(str_replace(s, srch[1:], rplc[1:]))
return rplc[0].join(reslst);

Related

Python - Efficiently replace characters within text file with ASCII characters [duplicate]

I can use this code below to create a new file with the substitution of a with aa using regular expressions.
import re
with open("notes.txt") as text:
new_text = re.sub("a", "aa", text.read())
with open("notes2.txt", "w") as result:
result.write(new_text)
I was wondering do I have to use this line, new_text = re.sub("a", "aa", text.read()), multiple times but substitute the string for others letters that I want to change in order to change more than one letter in my text?
That is, so a-->aa,b--> bb and c--> cc.
So I have to write that line for all the letters I want to change or is there an easier way. Perhaps to create a "dictionary" of translations. Should I put those letters into an array? I'm not sure how to call on them if I do.
The answer proposed by #nhahtdh is valid, but I would argue less pythonic than the canonical example, which uses code less opaque than his regex manipulations and takes advantage of python's built-in data structures and anonymous function feature.
A dictionary of translations makes sense in this context. In fact, that's how the Python Cookbook does it, as shown in this example (copied from ActiveState http://code.activestate.com/recipes/81330-single-pass-multiple-replace/ )
import re
def multiple_replace(dict, text):
# Create a regular expression from the dictionary keys
regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
# For each match, look-up corresponding value in dictionary
return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
if __name__ == "__main__":
text = "Larry Wall is the creator of Perl"
dict = {
"Larry Wall" : "Guido van Rossum",
"creator" : "Benevolent Dictator for Life",
"Perl" : "Python",
}
print multiple_replace(dict, text)
So in your case, you could make a dict trans = {"a": "aa", "b": "bb"} and then pass it into multiple_replace along with the text you want translated. Basically all that function is doing is creating one huge regex containing all of your regexes to translate, then when one is found, passing a lambda function to regex.sub to perform the translation dictionary lookup.
You could use this function while reading from your file, for example:
with open("notes.txt") as text:
new_text = multiple_replace(replacements, text.read())
with open("notes2.txt", "w") as result:
result.write(new_text)
I've actually used this exact method in production, in a case where I needed to translate the months of the year from Czech into English for a web scraping task.
As #nhahtdh pointed out, one downside to this approach is that it is not prefix-free: dictionary keys that are prefixes of other dictionary keys will cause the method to break.
You can use capturing group and backreference:
re.sub(r"([characters])", r"\1\1", text.read())
Put characters that you want to double up in between []. For the case of lower case a, b, c:
re.sub(r"([abc])", r"\1\1", text.read())
In the replacement string, you can refer to whatever matched by a capturing group () with \n notation where n is some positive integer (0 excluded). \1 refers to the first capturing group. There is another notation \g<n> where n can be any non-negative integer (0 allowed); \g<0> will refer to the whole text matched by the expression.
If you want to double up all characters except new line:
re.sub(r"(.)", r"\1\1", text.read())
If you want to double up all characters (new line included):
re.sub(r"(.)", r"\1\1", text.read(), 0, re.S)
You can use the pandas library and the replace function. I represent one example with five replacements:
df = pd.DataFrame({'text': ['Billy is going to visit Rome in November', 'I was born in 10/10/2010', 'I will be there at 20:00']})
to_replace=['Billy','Rome','January|February|March|April|May|June|July|August|September|October|November|December', '\d{2}:\d{2}', '\d{2}/\d{2}/\d{4}']
replace_with=['name','city','month','time', 'date']
print(df.text.replace(to_replace, replace_with, regex=True))
And the modified text is:
0 name is going to visit city in month
1 I was born in date
2 I will be there at time
You can find the example here
None of the other solutions work if your patterns are themselves regexes.
For that, you need:
def multi_sub(pairs, s):
def repl_func(m):
# only one group will be present, use the corresponding match
return next(
repl
for (patt, repl), group in zip(pairs, m.groups())
if group is not None
)
pattern = '|'.join("({})".format(patt) for patt, _ in pairs)
return re.sub(pattern, repl_func, s)
Which can be used as:
>>> multi_sub([
... ('a+b', 'Ab'),
... ('b', 'B'),
... ('a+', 'A.'),
... ], "aabbaa") # matches as (aab)(b)(aa)
'AbBA.'
Note that this solution does not allow you to put capturing groups in your regexes, or use them in replacements.
Using tips from how to make a 'stringy' class, we can make an object identical to a string but for an extra sub method:
import re
class Substitutable(str):
def __new__(cls, *args, **kwargs):
newobj = str.__new__(cls, *args, **kwargs)
newobj.sub = lambda fro,to: Substitutable(re.sub(fro, to, newobj))
return newobj
This allows to use the builder pattern, which looks nicer, but works only for a pre-determined number of substitutions. If you use it in a loop, there is no point creating an extra class anymore. E.g.
>>> h = Substitutable('horse')
>>> h
'horse'
>>> h.sub('h', 'f')
'forse'
>>> h.sub('h', 'f').sub('f','h')
'horse'
I found I had to modify Emmett J. Butler's code by changing the lambda function to use myDict.get(mo.group(1),mo.group(1)). The original code wasn't working for me; using myDict.get() also provides the benefit of a default value if a key is not found.
OIDNameContraction = {
'Fucntion':'Func',
'operated':'Operated',
'Asist':'Assist',
'Detection':'Det',
'Control':'Ctrl',
'Function':'Func'
}
replacementDictRegex = re.compile("(%s)" % "|".join(map(re.escape, OIDNameContraction.keys())))
oidDescriptionStr = replacementDictRegex.sub(lambda mo:OIDNameContraction.get(mo.group(1),mo.group(1)), oidDescriptionStr)
If you dealing with files, I have a simple python code about this problem.
More info here.
import re
def multiple_replace(dictionary, text):
# Create a regular expression from the dictionaryary keys
regex = re.compile("(%s)" % "|".join(map(re.escape, dictionary.keys())))
# For each match, look-up corresponding value in dictionaryary
String = lambda mo: dictionary[mo.string[mo.start():mo.end()]]
return regex.sub(String , text)
if __name__ == "__main__":
dictionary = {
"Wiley Online Library" : "Wiley",
"Chemical Society Reviews" : "Chem. Soc. Rev.",
}
with open ('LightBib.bib', 'r') as Bib_read:
with open ('Abbreviated.bib', 'w') as Bib_write:
read_lines = Bib_read.readlines()
for rows in read_lines:
#print(rows)
text = rows
new_text = multiple_replace(dictionary, text)
#print(new_text)
Bib_write.write(new_text)
Based on Eric's great answer, I came up with a more general solution that is capable of handling capturing groups and backreferences:
import re
from itertools import islice
def multiple_replace(s, repl_dict):
groups_no = [re.compile(pattern).groups for pattern in repl_dict]
def repl_func(m):
all_groups = m.groups()
# Use 'i' as the index within 'all_groups' and 'j' as the main
# group index.
i, j = 0, 0
while i < len(all_groups) and all_groups[i] is None:
# Skip the inner groups and move on to the next group.
i += (groups_no[j] + 1)
# Advance the main group index.
j += 1
# Extract the pattern and replacement at the j-th position.
pattern, repl = next(islice(repl_dict.items(), j, j + 1))
return re.sub(pattern, repl, all_groups[i])
# Create the full pattern using the keys of 'repl_dict'.
full_pattern = '|'.join(f'({pattern})' for pattern in repl_dict)
return re.sub(full_pattern, repl_func, s)
Example. Calling the above with
s = 'This is a sample string. Which is getting replaced. 1234-5678.'
REPL_DICT = {
r'(.*?)is(.*?)ing(.*?)ch': r'\3-\2-\1',
r'replaced': 'REPLACED',
r'\d\d((\d)(\d)-(\d)(\d))\d\d': r'__\5\4__\3\2__',
r'get|ing': '!##'
}
gives:
>>> multiple_replace(s, REPL_DICT)
'. Whi- is a sample str-Th is !##t!## REPLACED. __65__43__.'
For a more efficient solution, one can create a simple wrapper to precompute groups_no and full_pattern, e.g.
import re
from itertools import islice
class ReplWrapper:
def __init__(self, repl_dict):
self.repl_dict = repl_dict
self.groups_no = [re.compile(pattern).groups for pattern in repl_dict]
self.full_pattern = '|'.join(f'({pattern})' for pattern in repl_dict)
def get_pattern_repl(self, pos):
return next(islice(self.repl_dict.items(), pos, pos + 1))
def multiple_replace(self, s):
def repl_func(m):
all_groups = m.groups()
# Use 'i' as the index within 'all_groups' and 'j' as the main
# group index.
i, j = 0, 0
while i < len(all_groups) and all_groups[i] is None:
# Skip the inner groups and move on to the next group.
i += (self.groups_no[j] + 1)
# Advance the main group index.
j += 1
return re.sub(*self.get_pattern_repl(j), all_groups[i])
return re.sub(self.full_pattern, repl_func, s)
Use it as follows:
>>> ReplWrapper(REPL_DICT).multiple_replace(s)
'. Whi- is a sample str-Th is !##t!## REPLACED. __65__43__.'
I dont know why most of the solutions try to compose a single regex pattern instead of replacing multiple times. This answer is just for the sake of completeness.
That being said, the output of this approach is different than the output of the combined regex approach. Namely, repeated substitutions may evolve the text over time. However, the following function returns the same output as a call to unix sed would:
def multi_replace(rules, data: str) -> str:
ret = data
for pattern, repl in rules:
ret = re.sub(pattern, repl, ret)
return ret
usage:
RULES = [
(r'a', r'b'),
(r'b', r'c'),
(r'c', r'd'),
]
multi_replace(RULES, 'ab') # output: dd
With the same input and rules, the other solutions will output "bc". Depending on your use case you may or may not want to replace strings consecutively. In my case I wanted to rebuild the sed behavior. Also, note that the order of rules matters. If you reverse the rule order, this example would also return "bc".
This solution is faster than combining the patterns into a single regex (by a factor of 100). So, if your use-case allows it, you should prefer the repeated substitution method.
Of course, you can compile the regex patterns:
class Sed:
def __init__(self, rules) -> None:
self._rules = [(re.compile(pattern), sub) for pattern, sub in rules]
def replace(self, data: str) -> str:
ret = data
for regx, repl in self._rules:
ret = regx.sub(repl, ret)
return ret

Does python function str.find() returns string rather than index num

a = 'hello world'
a.find('wor')
# result: 5
which is index 5 .
this is not what i want. i want to return that particular sub string which has been found on index 5.
In Python how to search / find sub-string in a string that returns that sub-string if found not an index number.
Is there way out.....
Note that strings in Python are immutable, so if you somehow extract a substring from a source string a new string has to be created by copying the characters from the source string. It's not like in some other languages where you can simply return a reference into the source string.
To do what you want, I'd simply use the in operator. Eg:
a = 'hello world'
data = ('wor', 'WOR', 'hell', 'help')
for s in data:
print(s, s if s in a else None)
output
wor wor
WOR None
hell hell
help None
Or if you prefer a function:
def found(src, substring):
return substring if substring in src else None
for s in data:
print(s, found(a, s))
In case you're unfamiliar with Python's yes_value if some_condition else no_value syntax (aka a conditional expression), here's that function re-written using a more "traditional" if ... else block:
def found(src, substring):
if substring in src:
return substring
else:
return None
You can simply do like this:
myString = "hello world"
found = a.find('wor')
a = a[found: found+3]
of course just a simple example but should give you the idea, it uses python list slicing functionality, you can go one step further:
def find_substring(string, substring):
pos = string.find(substring)
return string[pos: pos + len(substring)]
This fulfills your stated requirement:
>>> import re
>>> (re.search('wor','Hello world')).group(0)
'wor'
>>>
(Read the Documentation for re.search for more information)

Apply formatting control characters (backspace and carriage return) to string, without needing recursion

What is the easiest way to "interpret" formatting control characters in a string, to show the results as if they were printed. For simplicity, I will assume there are no newlines in the string.
So for example,
>>> sys.stdout.write('foo\br')
shows for, therefore
interpret('foo\br') should be 'for'
>>>sys.sdtout.write('foo\rbar')
shows bar, therefore
interpret('foo\rbar') should be 'bar'
I can write a regular expression substitution here, but, in the case of '\b' replacement, it would have to be applied recursively until there are no more occurrences. It would be quite complex if done without recursion.
Is there an easier way?
If efficiency doesn't matter, a simple stack would work fine:
string = "foo\rbar\rbash\rboo\b\bba\br"
res = []
for char in string:
if char == "\r":
res.clear()
elif char == "\b":
if res: del res[-1]
else:
res.append(char)
"".join(res)
#>>> 'bbr'
Otherwise, I think this is about as fast as you can hope for in complex cases:
string = "foo\rbar\rbash\rboo\b\bba\br"
try:
string = string[string.rindex("\r")+1:]
except ValueError:
pass
split_iter = iter(string.split("\b"))
res = list(next(split_iter, ''))
for part in split_iter:
if res: del res[-1]
res.extend(part)
"".join(res)
#>>> 'bbr'
Note that I haven't timed this.
Python's does not have any built-in or standard library module for doing this.
However if you only care for simple control characters like \r, \b and \n you can write a simple function to handle this:
def interpret(text):
lines = []
current_line = []
for char in text:
if char == '\n':
lines.append(''.join(current_line))
current_line = []
elif char == '\r':
current_line.clear()
# del current_line[:] # in old python versions
elif char == '\b':
del current_line[-1:]
else:
current_line.append(char)
if current_line:
lines.append(current_line)
return '\n'.join(lines)
You can extend the function handling any control character you want. For example you might want to ignore some control characters that don't get actually displayed in a terminal (e.g. the bell \a)
UPDATE: after 30 minutes of asking for clarifications and an example string, we find the question is actually quite different: "How to repeatedly apply formatting control characters (backspace) to a Python string?"
In that case yes you apparently need to apply the regex/fn repeatedly until you stop getting matches.
SOLUTION:
import re
def repeated_re_sub(pattern, sub, s, flags=re.U):
"""Match-and-replace repeatedly until we run out of matches..."""
patc = re.compile(pattern, flags)
sold = ''
while sold != s:
sold = s
print "patc=>%s< sold=>%s< s=>%s<" % (patc,sold,s)
s = patc.sub(sub, sold)
#print help(patc.sub)
return s
print repeated_re_sub('[^\b]\b', '', 'abc\b\x08de\b\bfg')
#print repeated_re_sub('.\b', '', 'abcd\b\x08e\b\bfg')
[multiple previous answers, asking for clarifications and pointing out that both re.sub(...) or string.replace(...) could be used to solve the problem, non-recursively.]

Replacing leading text in Python

I use Python 2.6 and I want to replace each instance of certain leading characters (., _ and $ in my case) in a string with another character or string. Since in my case the replacement string is the same, I came up with this:
def replaceLeadingCharacters(string, old, new = ''):
t = string.lstrip(old)
return new * (len(string) - len(t)) + t
which seems to work fine:
>>> replaceLeadingCharacters('._.!$XXX$._', '._$', 'Y')
'YYY!$XXX$._'
Is there a better (simpler or more efficient) way to achieve the same effect in Python ?
Is there a way to achieve this effect with a string instead of characters? Something like str.replace() that stops once something different than the string-to-be-replaced comes up in the input string? Right now I've come up with this:
def replaceLeadingString(string, old, new = ''):
n = 0
o = 0
s = len(old)
while string.startswith(old, o):
n += 1
o += s
return new * n + string[o:]
I am hoping that there is a way to do this without an explicit loop
EDIT:
There are quite a few answers using the re module. I have a couple of questions/issues with it:
Isn't it significantly slower than the str methods when used as a replacement for them?
Is there an easy way to properly quote/escape strings that will be used in a regular expression? For example if I wanted to use re for replaceLeadingCharacters, how would I ensure that the contents of the old variable will not mess things up in ^[old]+ ? I'd rather have a "black-box" function that does not require its users to pay attention to the list of characters that they provide.
Your replaceLeadingCharacters() seems fine as is.
Here's replaceLeadingString() implementation that uses re module (without the while loop):
#!/usr/bin/env python
import re
def lreplace(s, old, new):
"""Return a copy of string `s` with leading occurrences of
substring `old` replaced by `new`.
>>> lreplace('abcabcdefabc', 'abc', 'X')
'XXdefabc'
>>> lreplace('_abc', 'abc', 'X')
'_abc'
"""
return re.sub(r'^(?:%s)+' % re.escape(old),
lambda m: new * (m.end() / len(old)),
s)
Isn't it significantly slower than the str methods when used as a replacement for them?
Don't guess. Measure it for expected input.
Is there an easy way to properly quote/escape strings that will be used in a regular expression?
re.escape()
re.sub(r'^[._$]+', lambda m: 'Y' * m.end(0), '._.!$XXX$._')
But IMHO your first solution is good enough.

How to do conditional character replacement within a string

I have a unicode string in Python and basically need to go through, character by character and replace certain ones based on a list of rules. One such rule is that a is changed to ö if a is after n. Also, if there are two vowel characters in a row, they get replaced by one vowel character and :. So if I have the string "natarook", what is the easiest and most efficient way of getting "nötaro:k"? Using Python 2.6 and CherryPy 3.1 if that matters.
edit: two vowels in a row does mean the same vowels (oo, aa, ii)
# -*- coding: utf-8 -*-
def subpairs(s, prefix, suffix):
def sub(i, sentinal=object()):
r = prefix.get(s[i:i+2], sentinal)
if r is not sentinal: return r
r = suffix.get(s[i-1:i+1], sentinal)
if r is not sentinal: return r
return s[i]
s = '\0'+s+'\0'
return ''.join(sub(i) for i in xrange(1,len(s)))
vowels = [(v+v, u':') for v in 'aeiou']
prefix = {}
suffix = {'na':u'ö'}
suffix.update(vowels)
print subpairs('natarook', prefix, suffix)
# prints: nötaro:k
prefix = {'na':u'ö'}
suffix = dict(vowels)
print subpairs('natarook', prefix, suffix)
# prints: öataro:k
focus on easy and correct first, then consider efficiency if profiling indicates its a bottleneck.
The simple approach is:
prev = None
for ch in string:
if ch == 'a':
if prev == 'n':
...
prev = ch
"I know, I'll use regular expressions!"
But seriously, regexes are really good for string manipulation.
You could write one per rule, like so:
s/na/nö/g
s/([aeiou])$1/$1:/g
Or you could generate them at runtime from some other source which lists them all.
Given your rules, I'd say you really want a simple state machine. Hmm, on second thought, maybe not; you can just look back in the string as you go.
I have a unicode string in Python and basically need to go through, character by character and replace certain ones based on a list of rules. One such rule is that a is changed to ö if a is after n. Also, if there are two vowel characters in a row, they get replaced by one vowel character and :. So if I have the string , what is the easiest and most efficient way of getting "nötaro:k"? Using Python 2.6 and CherryPy 3.1 if that matters.
vowel_set = frozenset(['a', 'e', 'i', 'o', 'u', 'ö'])
def fix_the_string(s):
lst = []
for i, ch in enumerate(s):
if ch == 'a' and lst and lst[-1] == 'n':
lst.append('ö')
else if ch in vowel_set and lst and lst[-1] in vowel_set:
lst[-1] = 'a' # "replaced by one vowel character", not sure what you want
lst.append(':')
else
lst.append(ch)
return "".join(lst)
print fix_the_string("natarook")
EDIT: Now that I saw the answer by #Anon. I think that's the simplest approach. This might actually be faster once you get a whole bunch of rules in play, as it makes one pass over the string; but maybe not, because the regexp stuff in Python is fast C code.
But simpler is better. Here is actual Python code for the regexp approach:
import re
pat_na = re.compile(r'na')
pat_double_vowel = re.compile(r'([aeiou])[aeiou]')
def fix_the_string(s):
s = re.sub(pat_na, r'nö', s)
s = re.sub(pat_double_vowel, r'\1:', s)
return s
print fix_the_string("natarook") # prints "nötaro:k"
It might be simpler to do with a handmade list of regular expressions, rather than progmatically gererating them. I recommend the following code.
import re
# regsubs is a dictionary of regular expressions as keys,
# and the replacement regexps as values
regsubs = {'na':u'nö',
'([aeiou])\\1': '\\1:'}
def makesubs(s):
for pattern, repl in regsubs.iteritems():
s = re.sub(pattern, repl, s)
return s
print makesubs('natarook')
# prints: nötaro:k

Categories