# Remove the last 'n' occurences of a substring from a string

Given a string and a substring and a positive integer n.

Remove the n occurences of a substring from the end of the originally given string. If the substring is not present in the string or the number of times the substring appears is less than n, then the output is the original string. No space should be present in place of the removed substring.

### RULES

1. The shortest code wins the challenge (as this is ).

### Test-cases:

Input:
String = "Cats do meow-meow while lions do not meow."
Substring = "meow"
n = 2

Output:
"Cats do meow- while lions do not ."

Input:
String = "Cats do not memeowow, they do meow-ing."
Substring = "meow"
n = 2

Output:
"Cats do not meow, they do -ing."

Input:
String = "Cats do meow-meow while lions do not meow."
Substring = "meow"
n = 4

Output:
"Cats do meow-meow while lions do not meow."

• What happens if removing a substring once would create more of it, like memeowow? What if matches overlap?
– xnor
Oct 25, 2022 at 7:49
• The phrase 'Remove the 'n' duplicates of substring from the end of' is also a bit confusing. When I first read it, I assumed that the substrings would be at the end of the string, not also contained within it. It only becomes clear from the examples... Oct 25, 2022 at 10:42
• @KevinCruijssen As made clear now in question, it would return the original string without doing anything. I am very sorry for not framing question properly. Oct 25, 2022 at 10:51
• Follow up question on the nested substrings. What is your desired result for Cats do meow not memeowow, they do meow-ing. where n=3?
– JvdV
Oct 25, 2022 at 19:49
• I think the term "duplicates" is also a little confusing; it also led me to expect that the substring was expected to be directly repeated n times, with nothing else between, at the end of the string. I would phrase this as "Remove n occurrences of the substring from the input. If the input contains more than than n occurrences of the substring, only the last n should be removed. If the input contains fewer than n occurrences, it should be returned unchanged".
– Ben
Oct 26, 2022 at 7:48

# Excel (ms365), 115, 83 bytes

-32 also thanks to @jdt and the idea to split instead of loop.

=LET(a,TEXTSPLIT(A1,,B1),b,ROWS(a),IF(b>C1,CONCAT(IF(SEQUENCE(b)<b-C1,a&B1,a)),A1))


Note: This answer has been edited to no longer loop to remove the substring from the end of the string, but split the input instead. This would now give the appropriate answer for input like 'Cats do meow not memeowow, they do meow-ing.' where 'n=3'.

# Python, 6653 52 bytes

• -1 byte thanks to @92.100.97.109 (though not exactly in the way they intended)
lambda i,j,k:[i,''.join(a:=i.rsplit(j,k))][len(a)>k]


Attempt This Online!

Returning identity when the occurrences are less than N is annoying.

• <s>Alternative 53 byte solution</s> Oct 25, 2022 at 14:29
• @97.100.97.109 Your version fails if the amount of occurrences exactly equals k but that's' fixable. Oct 25, 2022 at 14:31
• The funny thing is, that (the code that you edited to) is the code that I had originally written, but for some reason I thought it wasn't working properly? I don't know why Oct 25, 2022 at 14:33
• Your version flipped the condition I think, it didn't work properly in the equals case but I'm not really sure what I changed now. Oct 25, 2022 at 14:38
• 51 bytes feels like it could go even lower Oct 25, 2022 at 15:39

# C (clang), 109107112105 100 bytes

-11 bytes thanks to c--

-2 bytes thanks to ceilingcat

+5 bytes for fixing an error pointed out by JvdV.

f(*a,i,*b,j,n,**r){for(*r=wcsdup(a);i--*n>0;)!bcmp(a+i,b,j)?wcscpy(a+i,a+i+j),n--,i-=j:0;!n?*r=a:0;}


Try it online!

# T-SQL 135 bytes

Is not looping to avoid the string being created again "memeowow"

WITH c(z)as(SELECT 1UNION ALL
SELECT charindex(@r,@,z+1)FROM c WHERE z>0)SELECT top(@n)@=stuff(@,z,len(@r),'')FROM
c ORDER BY-z PRINT @


Try it online

### Python, 51 bytes

lambda A,B,n:"".join(A.rsplit(B,n*(n<=A.count(B))))


# Ruby, 6761 75 bytes

Updated after clarification by OP, which unfortunately made it longer.

->s,u,n,*a{s.scan(u){a<<$.size} a.size<n||eval("s[a.pop,u.size]='';"*n) s}  Attempt This Online! # 05AB1E, 13 bytes ¡.gI›I*F«}¹ý  Inputs in the order substring, string, n. Explanation: ¡ # Split the (implicit) second input-sentence on the (implicit) first substring  # Pop and push all parts separated to the stack .g # Get the amount of items on the stack I› # Check if this is larger than the third input-integer n I* # Multiply it by the third input-integer n F # Loop that many times: « # Concat the top two parts on the stack together }¹ý # After the loop: join the stack with the first input substring as delimiter # (after which the result is output implicitly)  • @TheThonnu Ah ok. That's just the way I make my comments for filters/maps/if-statement. I place the } below where it was opened. Here another example of that. Oct 25, 2022 at 12:55 • Oh, ok. Thanks for the clarification. Oct 25, 2022 at 13:02 # Pip, 30 bytes d:a^b#d>c?dZJ(bRL#d-UcALxRLc)a  Try It Online! How? d:a^b#d>c?dZJ(bRL#d-UcALxRLc)a a : First arg(string) b : Second arg(substring) c : Third arg(n) d: : Assign d to the result of a^b : Split a on separator b ? : If #d>c : Length of d is greater then c Uc : Increment c bRL : Repeat b #d- : Length of d - c times AL : Append list x : Empty String RL : Repeat x c : c times dZJ : Zip with iterable d a : Else return a  # JavaScript (ES6), 62 bytes Expects (string, substring, n). (s,q,n)=>(a=s.split(q))[n]+1?a.map(s=>a[++n]+1?s+q:s).join:s  Try it online! ### Commented (s, q, n) => ( // s = string, q = substring, n = integer a = s.split(q) // split s on q and save the result in a[] // if q appears x times, we get x + 1 entries // some of these entries may be empty strings (falsy) // so we do '+ 1' to distinguish between empty and undefined )[n] + 1 ? // if a[n] is defined: a.map(s => // for each entry s in a[]: a[++n] + 1 ? // increment n; if a[n] is still defined: s + q // append q to s : // else: s // leave s unchanged ).join // end of map(); join everything back together : // else: s // return s unchanged  # Vyxals, 11 bytes O≤[Ẇy?NẎY|_  Try it Online! 12 bytes without the flag. Takes substring, text, n ## Explained O≤[Ẇy?NẎY|_ O≤ # is the count of the substring in text less than n? [ | # if so: Ẇy # Split text on substring, keeping the delimiter. Then uninterleave - this gives a list of splits and a list of delimiters ?NẎ # Take up to the -nth item of the list of delimiters Y # Reinterleave the delimiters, which will now be shorter than before. The s flag combines all the pieces into a single sting |_ # Otherwise, just return the original text  # Desmos, 169 bytes S=s.length L=l.length I=[L-S...1] A=I[[(l[i...i+S]-s)^2.totalfori=I]=0] B=\{n>A.\length:[-L],A[1...n]\} f(l,s,n)=l[[[0^{(b-B)^2}.maxforb=z-[0...S-1]].maxforz=[1...L]]=0]  Try It On Desmos! Try It On Desmos! - Prettified $$\f(l,s,n)\$$ takes in two lists of codepoints, with $$\l\$$ representing the string and $$\s\$$ representing the substring, and a positive integer $$\n\$$, representing the number of substrings to remove. The output is also a list of codepoints. As you can tell by the byte size of the code, Desmos is not really well suited for problems related with string manipulation (When was using Desmos on a string problem ever a good idea?!?!). Also fun fact, this is my first time using a nested list comprehension in Desmos, which is pretty nice. There's probably a better way of doing that part, but nested list comprehensions look cool :P (to be completely honest, I can't think of any better way to do that lol). # Red, 89 bytes func[s t n][reverse t either parse r: reverse copy s[n to remove t to end][reverse r][s]]  Try it online! Working on a reversed copy of the string, I use parse to remove n times the reversed substring. If parse succeeds, I return the modified string reversed, otherwise - the original string. # Japt v2.0a0, 21 bytes qW ÔË+WpE>VÃw qWpUÊ§V  Try it Input as string, n, substring v2.0a0 is necessary because v1.4.6 errors if you try to use p this way. Explanation: qW ÔË+WpE>VÃw qW # Split <string> where <substring> appears Ô # Reverse the array Ë Ã # For each item in the array: + # Append: Wp # <substring> repeated a number of times equal to: E>V # The current index is greater than <n> # (Boolean gets converted to 1 or 0) w # Reverse the new array # Store as U qWpUÊ§V q # Turn U into a string by inserting this between each item: Wp # <substring> repeated a number of times equal to: UÊ # Length of U (i.e. 1 + number of times <substring> appeared) §V # Is less than or equal to <n> # Output that string  I've tried an alternate using ð but the best I got was 25 bytes. # Pyth, 21 bytes s.iJcwz*]zt-lJ*KE>lJK  Try it online! ### Explanation s concatenate the elements of .i interleave Jcwz the input string split on the substring (assigned to J) and ]z a singleton list of the substring * duplicated t one less than -lJ the length of J minus *KE>lJK 0 if substring has less than n occurrences, n otherwise  # simply, 99 bytes Creates an anonymous function that returns the expected result. The argument order is $full_string, $substring and then $number.

fn($F$S$N)&iff(run&len(&str_split($F$S))>$N&str_rev(&str_replace(&str_rev($F)&str_rev($S)''$N))$F);


## How does it work?

First, splits the full string by the substring, and counts the number of elements.

When splitting the string:

• If the substring isn't present, returns 1 element.
• If it is present, returns 1 extra element.
Example: &str_split("abcbdbebf", 'b') -> ["a", "c", "d", "e", "f"]
For 4 'b's, returns 5 elements.

If the number of elements is higher than $number, that means there's at least $number substrings in the full string.

The &iff() function is just the same as a ternary operator, in C-like languages.

To remove from the end of the string, I need to reverse the string, since there isn't a way to indicate to remove from the end of the string.
However, it takes an argument that indicates how many times to replace, which means, it will replace up to $number occurrences. The result is returned automatically, since it is a function without a scope, just like an arrow function in JavaScript. ## Ungolfed Code-y looking: $fn = fn($full_string,$substring, $num) => &iff( (call &len(&str_split($full_string, $substring))) >$num,
&str_rev(
&str_replace(
&str_rev($full_string), &str_rev($substring),
'', $num ) ),$full_string
);


Pseudo-code looking:

Set $fn to an anonymous function ($full_string, $substring,$num)
Begin.
Set $count to the result of calling &len(Call &str_split($full_string, $substring)). Return the result of calling &iff($count > $num, Call &str_rev( Call &str_replace( Call &str_rev($full_string),
Call &str_rev($substring), '',$num
)
),
$full_string ). End.  # Retina 0.8.2, 87 bytes .+$
$* (?=.*¶(.+)¶)\1 ¶ rT¶(?(1)$)(?<-1>¶.*)+(?=¶.+¶(1)+$) +¶(.*¶(.+)¶1+$)
$2$1
1G


Try it online! Takes the string, substring and count on three separate lines. Explanation:

.+*


Convert the count to unary.

(?=.*¶(.+)¶)\1
¶


Replace occurrences of the substring in the string with newlines.

rT¶(?(1)$)(?<-1>¶.*)+(?=¶.+¶(1)+$)


Remove the last n newlines from the string if there are in fact that many. (Today I discovered that an empty "to" string in transliteration is treated as _, i.e. delete matching characters.) The r causes the regular expression to be evaluated from right to left, so the lookahead gets processed first (setting $#1), then the newline-matching loop in the middle, then the count check at the beginning. +¶(.*¶(.+)¶1+$)
$2$1


Replace (remaining) newlines with the substring.

1G


Keep only the (final) string.

# Charcoal, 25 bytes

Ｎθ≔⪪ηζηＦ›Ｌηθ⊞η⪫⮌Ｅ⊕θ⊟ηω⪫ηζ


Try it online! Link is to verbose version of code. Takes inputs in the order count, string, substring. Explanation:

Ｎθ


Input the count as an integer.

≔⪪ηζη


Split the string on the substring.

Ｆ›Ｌηθ


If this results in more than enough pieces, then...

⊞η⪫⮌Ｅ⊕θ⊟ηω


... join the last n+1 pieces together.

⪫ηζ


Join the (remaining) pieces with the substring.

# PHP 8.x, 65 bytes

This anonymous function does the replacement, up to $n, and checks how many replacements were made. If there were fewer than $n replacements made, returns the original string.

fn($F,$S,$N)=>($r=preg_replace("@$S@",'',$F,$N,$x))&&$N>$x?$F:$r;


Due to the use of preg_replace, the substring must be a valid regular expression.
Regular words will always be valid regular expressions, and there's no need to worry about it.

## How does it work?

The function preg_replace takes the following arguments:

• $pattern - Which will be the substring, $S
• $replacement - Which is an empty string • $subject - The full string, $F • $limit - Maximum number of replacements, $N • &$count - Variable which will hold the number of replacements made

If $N is higher than $count, returns the full string, otherwise returns the result of the replacement.

# Example

$fn=fn($F,$S,$N)=>($r=preg_replace("@$S@",'',$F,$N,$x))&&$N>$x?$F:$r; echo$fn('Cats do not memeowow, they do meow-ing.', 'meow', 2);
// Should display: Cats do not meow, they do -ing.


You can try this on https://onlinephp.io/c/d08f3

# PHP, 75 111 bytes

Okay so... lot of change

+12 bytes for a bug found by @Ismael Miguel where loop keep running if no instance of searched string given (strrpos()===false)

+26 bytes for using $argv instead or pre defined variable as indicated by @Sʨɠɠan -2 bytes (Hey! an actual upgrade :D) from @Sʨɠɠan who remind me i can just get ride of {} for a single line instruction ^^' for($s=$argv[1];$argv[3]--&&false!==$c=strrpos($s,$argv[2]);)$s=substr($s,0,$c).substr($s,$c+strlen($argv[2]));  Try it online! • If you don't mind using older versions, like 5.3-5.6 or some 7.x versions, you can reduce by 2 bytes by removing the double quotes. Oct 25, 2022 at 14:18 • Also, you have a bug. The rules say: "If the substring is not present in the string or the number of times the substring appears is less than n, then the output is the original string.". For an n = 5, on test case 0, it displays "not , they do -ing.". Oct 25, 2022 at 14:23 • You don't need the {}. Oct 26, 2022 at 1:36 • However, by our consensus, we can't take input/output via a variable. You can try taking input via ARGV, though. That's often shorter than a function Oct 26, 2022 at 1:37 • BTW, it's actually shorter to reference substr twice than storing it in a variable, even without the quotes. Oct 26, 2022 at 1:38 # Raku, 42 bytes {$^a.flip.subst($^b.flip,'',:x($^c)).flip}


Try it online!

• Welcome to Code Golf! Nov 1, 2022 at 19:54

# Shell, 238236220200199194191 186 bytes

(counting the last newline to make the script file containing those commands a real unix text file. Otherwise 185 bytes.)

Can probably be highly optimized, but I found it funny to attempt.

usage: cat Text-cases | this_script , or this_script < Test-cases

sed -e'/^O/N;s,\n,,;s,",,g;s, = ,:,g'|awk -F: '/^St/{I=$2} /^Su/{S=$2}
/^n/{n=$2 R=s="" for(i=1;i<=n;i++){R=R sprintf("\$$.*\$$%s",S) s=s"\\"i} system("echo "I"|sed -e\"s,"R","s",\"")}'  Explanation: the sed : 1) put the line following "Output:" at the end of that line 2) takes out the doublequotes 3) replace " = " by ":" Then the awk: retrieve (I)nput String, (S)ubstring, and (n), loops on n to construct: the (r)egex "$$.*$$S$$.*$$S$$.*$$S" (if n==3) and its (s)ubstitution "\1\2\3" and use sed to do this search/replace on the (I)nput string. I used "\n" instead of ";" when I could (in the awk part) as it is 1 caracter all the same, and is more legible.  # Perl 5, 74 bytes sub{($_,$s,$n)=@_;$n*2>(@a=split/($s)/)?$_:join'',grep!/$s/||++\$n*2<@a,@a}


Try it online!

# Ruby, 64 bytes

->s,u,n,*a{x=s.split(u);l=x.size-n;x[0...l].join(u)+x[l..].join}

Try it online!

Code is mine - I thought split and join would be more efficient, but I will credit Jordan above for the tests and framework.

# K (ngn/k), 46 bytes

{x(!#x)^,/(!#y)+/:(-z*(~z>#t))#t:&(#y)(y~)':x}


Try it online!

# Python 3, 78 bytes

i=input
a=i()
b=i()
c=int(i())
d=a.rsplit(b,c)
print([a,''.join(d)][len(d)>c])
`

Pretty direct approach using Python 3. Try it online!