python - Replace nth occurrence of substring in string - Stack Overflow
How can I replace the nth occurence of a substring/character within a string? [Python 3] - Stack Overflow
python - replace the nth character of a string (RegEx or not) - Stack Overflow
python - Replace every nth letter in a string - Stack Overflow
You can use a while loop with str.find to find the nth occurrence if it exists and use that position to create the new string:
def nth_repl(s, sub, repl, n):
find = s.find(sub)
# If find is not -1 we have found at least one match for the substring
i = find != -1
# loop util we find the nth or we find no match
while find != -1 and i != n:
# find + 1 means we start searching from after the last match
find = s.find(sub, find + 1)
i += 1
# If i is equal to n we found nth match so replace
if i == n:
return s[:find] + repl + s[find+len(sub):]
return s
Example:
In [14]: s = "foobarfoofoobarbar"
In [15]: nth_repl(s, "bar","replaced",3)
Out[15]: 'foobarfoofoobarreplaced'
In [16]: nth_repl(s, "foo","replaced",3)
Out[16]: 'foobarfooreplacedbarbar'
In [17]: nth_repl(s, "foo","replaced",5)
Out[17]: 'foobarfoofoobarbar'
I use simple function, which lists all occurrences, picks the nth one's position and uses it to split original string into two substrings. Then it replaces first occurrence in the second substring and joins substrings back into the new string:
import re
def replacenth(string, sub, wanted, n):
where = [m.start() for m in re.finditer(sub, string)][n-1]
before = string[:where]
after = string[where:]
after = after.replace(sub, wanted, 1)
newString = before + after
print(newString)
For these variables:
string = 'ababababababababab'
sub = 'ab'
wanted = 'CD'
n = 5
outputs:
ababababCDabababab
Notes:
The
wherevariable actually is a list of matches' positions, where you pick up the nth one. But list item index starts with0usually, not with1. Therefore there is an-1index andnvariable is the actual nth substring. My example finds 5th string. If you usenindex and want to find 5th position, you'll neednto be4. Which you use usually depends on the function, which generates ourn.
This should be the simplest way, but maybe it isn't the most Pythonic way, because the
wherevariable construction needs importingrelibrary. Maybe somebody will find even more Pythonic way.
Sources and some links in addition:
whereconstruction: How to find all occurrences of a substring?- string splitting: https://www.daniweb.com/programming/software-development/threads/452362/replace-nth-occurrence-of-any-sub-string-in-a-string
- similar question: Find the nth occurrence of substring in a string
This might not be the most concise way, you can find all the indices of b, take every 5th one, and then assign c. Since indices inside str are not assignable, you have to convert to list.
jStr = 'aabbbbbaa'
jStr = list(jStr)
bPos = [x for x in range(len(jStr)) if jStr[x] == 'b']
for i,x in enumerate(bPos):
if (i+1) % 5 == 0:
jStr[x] = 'c'
jStr = ''.join(jStr)
print(jStr)
Output:
aabbbbcaa
jStr = "aabbbbbaabbbbb"
count = 1
res= "" # strings are immutable so we have to create a new string.
for s in jStr:
if count == 5 and s == "b": # if count is 5 we have our fifth "b", change to "c" and reset count
res += "c"
count = 1
elif s == "b": # if it is a "b" but not the fifth just add b to res and increase count
count += 1
res += "b"
else: # else it is not a "b", just add to res
res += s
print(res)
aabbbbcaabbbbc
Finds every fifth b, counting the b's using count, when we have reached the fifth we reset the counter and go on to the next character.
I would approach this a bit differently:
The state at each point in time is defined by:
- the full phrase (to be guessed),
- a set of hidden words,
- letters guessed so far (or to be revealed to the user).
The you can define a show() function with these three quantities:
def show(phrase, hidden_words, letters_guessed):
parts = [
''.join([
c if c in letters_guessed else '-' for c in w
]) if w in hidden_words else w
for w in phrase.split()
]
return ' '.join(parts)
With this, you can write tests, including doctests. That will make your code much easier to document, debug, test and use.
Some examples (which could be included as doctests in the docstring of show):
phrase = 'TO PIPE (STH) UP AND PIPE DOWN'
hidden_words = {'PIPE', 'UP'}
>>> show(phrase, hidden_words, {})
'TO ---- (STH) -- AND ---- DOWN'
>>> show(phrase, hidden_words, set('I'))
'TO -I-- (STH) -- AND -I-- DOWN'
>>> show(phrase, hidden_words, set('PI'))
'TO PIP- (STH) -P AND PIP- DOWN'
Given you're a rookie, I'd thought throw my hat in and do it without regex and attempt to explain. Maybe only read this once you've had your go. Your question title can be answered with:
def replace_nth_char(string, n, replacement):
n -= 1
l_string = list(string)
l_string[n] = replacement
return ''.join(l_string)
It converts the string to a list of letters which you can replace by index n and then joins it back up.
I've also given the rest a go to show you more python options.
As you're managing the state, you might want to think about using a class. It helps you wrap all the functions and attributes together into one object with a purpose - playing your Vocabulary Game. I recommend looking into them. Here is one for the game:
import random
class VocabularyGame():
def __init__(self, chosen_words, hidden_words):
self.chosen_words = chosen_words
self.hidden_words = hidden_words
self.hidden_letters = list(set(''.join(words)))
self.masked_sentence = self.mask_words(chosen_words, hidden_words)
print(f"Game start: {self.masked_sentence}")
def mask_words(self, sentence, masks):
return ' '.join(['_'*len(w) if w in masks else w for w in sentence.split(' ')])
def try_letter(self, letter=None):
if letter is None:
letter = random.choice(self.hidden_letters)
self.masked_sentence = ''.join(
[c if c== letter else m for m, c in zip(self.masked_sentence, self.chosen_words)]
)
self.hidden_letters = [l for l in self.hidden_letters if l != letter]
print(f"Trying letter {letter}...\nRemaining letters: {self.masked_sentence}")
The __init__ section runs whenever we make new game instances and takes three arguments, (self, chosen_words, hidden_words). The use of self references the current class instance (or game) and we can use it to set and retrieve attributes to the class, in this case, to remember our words and sentences.
def __init__(self, chosen_words, hidden_words):
self.chosen_words = chosen_words
self.hidden_words = hidden_words
self.hidden_letters = list(set(''.join(hidden_words)))
self.masked_sentence = self.mask_words(chosen_words, hidden_words)
print(f"Game start: {self.masked_sentence}")
list(set(''.join(words))) gets all unique letters in the hidden words by joining them into one string and using sets to convert them into the unique letters. I convert this back into a list for ease of use later.
We then apply a function to mask the words with '_'.
def mask_words(self, sentence, masks):
return ' '.join(['_'*len(w) if w in masks else w for w in sentence.split(' ')])
This goes through each word in the sentence and replaces it with '_' for the length of the word, if it exists in hidden words. Then it joins it back up. Now we have our start state.
The last thing to do is to try a letter. We do this by defining a method (function on a class) def try_letter(self, letter=None):. If no letter is provided, we pick a random one from the unique missing letters we defined earlier.
The we go through each letter in the original sentence and masked sentence together using zip and when the original letter is our chosen letter, we replace the one in our masked sentence.
self.masked_sentence = ''.join(
[c if c==letter else m for m, c in zip(self.masked_sentence, self.chosen_words)]
)
Then remove the letter form our hidden letters list:
self.hidden_letters = [l for l in self.hidden_letters if l != letter]
Finally, we print result out using f-strings. Now, we can play the game!
chosen_word = "TO CRANK (STH) UP"
words = ['CRANK', 'UP']
game = VocabularyGame(chosen_word, words)
Outputs: Game start: TO _____ (STH) __
Trying a letter 7 times for i in range(7): game.try_letter()
Outputs:
Trying letter N...
Remaining letters: TO ___N_ (STH) __
Trying letter K...
Remaining letters: TO ___NK (STH) __
Trying letter R...
Remaining letters: TO _R_NK (STH) __
Trying letter P...
Remaining letters: TO _R_NK (STH) _P
Trying letter C...
Remaining letters: TO CR_NK (STH) _P
Trying letter U...
Remaining letters: TO CR_NK (STH) UP
Trying letter A...
Remaining letters: TO CRANK (STH) UP
One-liner:
newstring = ''.join("*" if i % n == 0 else char for i, char in enumerate(string, 1))
Expanded:
def replace_n(string, n, first=0):
letters = (
# i % n == 0 means this letter should be replaced
"*" if i % n == 0 else char
# iterate index/value pairs
for i, char in enumerate(string, -first)
)
return ''.join(letters)
>>> replace_n("hello world", 4)
'*ell* wo*ld'
>>> replace_n("hello world", 4, first=-1)
'hel*o w*orl*'
Your code has several problems:
First, the return in the wrong place. It is inside the for loop but it should be outside.
Next, in the following fragment:
for i in range(len(str)):
n=str[i]
newStr=str.replace(n, "*")
the n that you passed as the second argument to your function is being overwritten at every loop step. So if your initial string is "abcabcabcd" and you pass n=3 (a number) as a second argument what your loop does is:
n="a"
n="b"
n="c"
...
so the value 3 is never used. In addition, in your loop only the last replacement done in your string is saved:
n="a"
newStr="abcabcabcd".replace("a", "*") --> newStr = "*bc*bc*bcd"
n="b"
newStr="abcabcabcd".replace("b", "*") --> newStr = "a*ca*ca*cd"
...
n="d"
newStr="abcabcabcd".replace("d", "*") --> newStr = "abcabcabc*"
If you test your function (after fixing the return position) with some strings it seems to work fine:
In [7]: replaceN("abcabcabc", 3)
Out[7]: 'ab*ab*ab*'
but if you do the choice more carefully:
In [10]: replaceN("abcabcabcd", 3)
Out[10]: 'abcabcabc*'
then it is obvious that the code fails and it is equivalent to replace only the last character of your string:
my_string.replace(my_string[-1], "*")
The code given by Eric is working fine:
In [16]: ''.join("*" if i % 3 == 0 else char for i, char in enumerate("abcabcabcd"))
Out[16]: '*bc*bc*bc*'
It replaces positions 3rd, 6th, 9th and so on. It may need some adjustment if you don't want the position 0 being replaced too.
I guess you may do something like this:
nreplace=1
my_string="hello my friend"
words=my_string.split(" ")
words[nreplace]="your"
" ".join(words)
Here is another way of doing the replacement:
nreplace=1
words=my_string.split(" ")
" ".join([words[word_index] if word_index != nreplace else "your" for word_index in range(len(words))])
Let's say your string is:
my_string = "This is my test string."
You can split the string up using split(' ')
my_list = my_string.split()
Which will set my_list to
['This', 'is', 'my', 'test', 'string.']
You can replace the 4th list item using
my_list[3] = "new"
And then put it back together with
my_new_string = " ".join(my_list)
Giving you
"This is my new string."
I'm trying to work through a Mad Libs practice problem that seems like it should have a simple solution that I can't figure out.
I have a string of text containing placeholder words that the user is prompted to replace. Each word that the user inputs will be different. Is there a way to call them in order within re.sub()? An example of what I tried doing here:
import re
text = 'The ADJECTIVE brown fox jumps over the lazy NOUN.'
myRegex = re.compile(r'ADJECTIVE|NOUN')
wordPrompts = myRegex.findall(text)
user_inputs = []
for prompt in wordPrompts:
user_inputs.append(input(f'Enter a {prompt.lower()}:\n'))
output = myRegex.sub(user_inputs[nth match goes here?], text)
print(output)Python strings are immutable, which means that they do not support item or slice assignment. You'll have to build a new string using i.e. someString[:3] + 'a' + someString[4:] or some other suitable approach.
Instead of storing your value as a string, you could use a list of characters:
>>> l = list('foobar')
>>> l[3] = 'f'
>>> l[5] = 'n'
Then if you want to convert it back to a string to display it, use this:
>>> ''.join(l)
'foofan'
If you are changing a lot of characters one at a time, this method will be considerably faster than building a new string each time you change a character.
I'm trying to replace every third character in a string, but it's not working. Here is my code:
s=str(input())
dv3=[::3]
print(s.replace(dv3,"a"))