g | x | w | all
Bytes Lang Time Link
087AWK241205T211205Zxrs
422Scala 3240507T134331Z138 Aspe
052Perl 5 pl240506T142742ZXcali
108R240504T170911Zint 21h
078Python 3221219T125207ZU13-Forw
046K ngn/k221212T105027Zdoug
064Ruby221212T010254ZJeremy
100C clang221025T154740Zjdt
042Raku221101T194313Zandm
074Perl 5221031T220948ZKjetil S
nanShell221027T190452ZOlivier
021Japt v2.0a0221025T162225ZKamil Dr
111PHP221025T135424ZRatchet2
075Ruby221025T132704ZJordan
051Python221026T135821ZTeck-fre
065PHP 8.x221026T111337ZIsmael M
083Excel ms365221025T141005ZJvdV
089Red221026T064407ZGalen Iv
169Desmos221026T061329ZAiden Ch
011Vyxal s221026T005446Zlyxal
025Charcoal221026T001434ZNeil
087Retina 0.8.2221025T235809ZNeil
062JavaScript ES6221025T151548ZArnauld
099simply221025T141650ZIsmael M
030Pip221025T152653ZBaby_Boy
021Pyth221025T144218ZCursorCo
nanPython221025T074431Zmousetai
135TSQL221025T090009Zt-clause
01305AB1E221025T072434ZKevin Cr

AWK, 87 bytes

{for(a=$1;sub($2,"",$1);)x++;if(x>$3)for(;$3--;)a=gensub("(.*)("$2")","\\1","g",a)}$0=a

Attempt This Online!

{for(a=$1;sub($2,"",$1);)  # count occurrences of key
x++;if(x>$3)               # compare to n
for(;$3--;)                # count down from n
a=gensub("(.*)("$2")","\\1","g",a)
                           # regex to drop last
}$0=a                      # set output

Scala 3, 422 bytes

A port of @mousetail's Python answer in Scala.

Use user-defined rsplit.


422 bytes, it can be golfed more.


Golfed version. Attempt This Online!

def r(s: String, d: String, m: Int) = {
  var (r, a, n) = (List[String](), s, m)
  while (n > 0) {
    a.lastIndexOf(d) match {
      case -1 => n = 0; r ::= a; a=""
      case p => r ::= a.substring(p + d.size); a = a.substring(0, p); n -= 1
    }
  }
  (if (a.nonEmpty || m == 0) a :: r else r).reverse.toArray
}
def f(s: String, d: String, m: Int) = {
  val p = r(s, d, m)
  if (p.size > m) p.mkString("").trim else s
}

Ungolfed version. Attempt This Online!

object Main {
  def rsplit(string: String, delimiter: String, maxSplits: Int): Array[String] = {
    var remainingSplits = maxSplits
    var str = string
    var result = List[String]()

    while (remainingSplits > 0) {
      val pos = str.lastIndexOf(delimiter)
      if (pos == -1) {
        result = str :: result
        remainingSplits = 0
        str=""
      } else {
        result = str.substring(pos + delimiter.length) :: result
        str = str.substring(0, pos)
        remainingSplits -= 1
      }
    }

    if (str.nonEmpty || maxSplits == 0) {
      result = str :: result
    }

    result.toArray
  }

  def splitAndJoin(string: String, delimiter: String, maxSplits: Int): String = {
    val splitParts = rsplit(string, delimiter, maxSplits)
    if (splitParts.length > maxSplits) splitParts.mkString("") else string
  }

  def main(args: Array[String]): Unit = {
    // Test cases
    println(splitAndJoin("bor tor tor tor", "tor", 2))  // More than maxSplits
    println(splitAndJoin("bor tor tor", "tor", 2))      // Exactly equal to maxSplits
    println(splitAndJoin("bor tor", "tor", 2))      // Less than maxSplits
  }
}

Perl 5 -pl, 52 bytes

$t=<>;$c=<>;chop$t;s/.*\K($t.*?){$c}/$&=~s|$t||gr/ge

Try it online!

R, 124 109 108 bytes (new)

f=\(S,s,n,`?`=\(x)intToUtf8(rev(utf8ToInt(x))))`if`(sum(el(gregexpr(s,S))|1)>=n&n,f(?sub(?s,"",?S),s,n-1),S)

Attempt This Online!

Edit:

The old version of my golf has incorrectly treated a case, when the substring was matching exactly the end of the string. Therefore I have abandoned the old approach.

In this new version a chain of functions is used to reverse the string as well as the substring, sub is applied n times and then the string get reversed again.

Below is the old version.

R, 100 bytes
\(S,s,n,m=length(V<-el(strsplit(S,s))))
`if`(m>n,paste0(V,c(rep(s,m-1-n),rep("",n+1)),collapse=""),S)

Attempt This Online!

This answer is similar to the answer in Python. The main string S is splitted by a substring s, and then combined with a same length vector containing a reduced by n number of substrings.

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!

K (ngn/k), 46 bytes

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

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.

C (clang), 109 107 112 105 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!

Raku, 42 bytes

{$^a.flip.subst($^b.flip,'',:x($^c)).flip}

Try it online!

Perl 5, 74 bytes

sub{($_,$s,$n)=@_;$n*2>(@a=split/($s)/)?$_:join'',grep!/$s/||++$n*2<@a,@a}

Try it online!

Shell, 238 236 220 200 199 194 191 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.

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.

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!

Ruby, 67 61 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!

Python, 51 bytes

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

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:

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

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))

enter image description here


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'.

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.

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).

Vyxal s, 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

Charcoal, 25 bytes

Nθ≔⪪ηζηF›Lηθ⊞η⪫⮌E⊕θ⊟ηω⪫ηζ

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

Nθ

Input the count as an integer.

≔⪪ηζη

Split the string on the substring.

F›Lηθ

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

⊞η⪫⮌E⊕θ⊟ηω

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

⪫ηζ

Join the (remaining) pieces with the substring.

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.

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

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 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.

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

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

Python, 66 53 52 bytes

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.

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

05AB1E, 13 bytes

¡`.gI›I*F«}¹ý

Inputs in the order substring, string, n.

Try it online or verify some more 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)