146

See my code snippet below:

var list = ['one', 'two', 'three', 'four'];
var str = 'one two, one three, one four, one';
for ( var i = 0; i < list.length; i++)
{
     if (str.endsWith(list[i])
     {
         str = str.replace(list[i], 'finish')
     }
 }

I want to replace the last occurrence of the word one with the word finish in the string, what I have will not work because the replace method will only replace the first occurrence of it. Does anyone know how I can amend that snippet so that it only replaces the last instance of 'one'

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ruth
  • 5,646
  • 12
  • 38
  • 45

16 Answers16

162

Well, if the string really ends with the pattern, you could do this:

str = str.replace(new RegExp(list[i] + '$'), 'finish');
Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 4
    Good idea. Need to escape that string first (though not with her sample data, granted), which is non-trivial. :-( – T.J. Crowder Apr 28 '10 at 13:13
  • 1
    @Ruth no prob! @TJ yes, indeed that's true: Ruth if you end up with "words" you're looking for that include the special characters used for regular expressions, you'd need to "escape" those, which as TJ says is a little tricky (not impossible though). – Pointy Apr 28 '10 at 21:56
  • 1
    what if you want to replace the last occurance of string1 inside string2? – SuperUberDuper Mar 21 '15 at 21:17
  • @SuperUberDuper try `str.replace(new RegExp("(.*)" + list[i]), "$1replacement-goes-here")` – Pointy Mar 21 '15 at 21:22
  • is this valid? new RegExp('/' + self.get("foo") + '/'); – SuperUberDuper Mar 21 '15 at 21:24
  • 1
    @SuperUberDuper well it is if you want to match something surrounded by "/" characters. There are two ways to make a RegExp instance: the constructor (`new RegExp(string)`), and the inline syntax (`/something/`). You don't need the "/" characters when you're using the RegExp constructor. – Pointy Mar 21 '15 at 21:26
  • @SuperUberDuper ah well if you're building a RegExp from pieces, then you're stuck with the `new RegExp()` method - the inline syntax is for regular expressions that are essentially constants. – Pointy Mar 21 '15 at 21:29
  • how can I make my regex replace the last occurrence? **new RegExp(".*" + (fooVar)));** and then **var newText = currentText.replace(re, foo1 || foo2);** – SuperUberDuper Mar 21 '15 at 21:36
  • @SuperUberDuper Why don't you ask a new question? It's difficult to provide readable answers here in the comment stream :) – Pointy Mar 21 '15 at 21:40
  • https://stackoverflow.com/questions/29188409/how-do-i-replace-the-last-ocurance-of-a-variable-in-a-string – SuperUberDuper Mar 21 '15 at 21:43
  • @Pointy Hi I was testing some stuff with your code. I realized that if the `list[i]` has a `^` character, then It stops working. why is that? can we get around it ? – TechLife Apr 13 '15 at 08:40
  • 3
    @TechLife you'd have to apply a transformation to the string to "protect" all the regex metacharacters with `\` - things like `+`, `*`, `?`, parentheses, square brackets, maybe other things. – Pointy Apr 13 '15 at 12:47
50

You can use String#lastIndexOf to find the last occurrence of the word, and then String#substring and concatenation to build the replacement string.

n = str.lastIndexOf(list[i]);
if (n >= 0 && n + list[i].length >= str.length) {
    str = str.substring(0, n) + "finish";
}

...or along those lines.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 2
    Another example: var nameSplit = item.name.lastIndexOf(", "); if (nameSplit != -1) item.name = item.name.substr(0, nameSplit) + " and "+ item.name.substr(nameSplit + 2); – Ben Gotow Feb 13 '12 at 17:52
16

Not as elegant as the regex answers above, but easier to follow for the not-as-savvy among us:

function removeLastInstance(badtext, str) {
    var charpos = str.lastIndexOf(badtext);
    if (charpos<0) return str;
    ptone = str.substring(0,charpos);
    pttwo = str.substring(charpos+(badtext.length));
    return (ptone+pttwo);
}

I realize this is likely slower and more wasteful than the regex examples, but I think it might be helpful as an illustration of how string manipulations can be done. (It can also be condensed a bit, but again, I wanted each step to be clear.)

WilsonCPU
  • 161
  • 1
  • 2
13

Here's a method that only uses splitting and joining. It's a little more readable so thought it was worth sharing:

    String.prototype.replaceLast = function (what, replacement) {
        var pcs = this.split(what);
        var lastPc = pcs.pop();
        return pcs.join(what) + replacement + lastPc;
    };
mr.freeze
  • 13,731
  • 5
  • 36
  • 42
11

Thought I'd answer here since this came up first in my Google search and there's no answer (outside of Matt's creative answer :)) that generically replaces the last occurrence of a string of characters when the text to replace might not be at the end of the string.

if (!String.prototype.replaceLast) {
    String.prototype.replaceLast = function(find, replace) {
        var index = this.lastIndexOf(find);

        if (index >= 0) {
            return this.substring(0, index) + replace + this.substring(index + find.length);
        }

        return this.toString();
    };
}

var str = 'one two, one three, one four, one';

// outputs: one two, one three, one four, finish
console.log(str.replaceLast('one', 'finish'));

// outputs: one two, one three, one four; one
console.log(str.replaceLast(',', ';'));
Irving
  • 1,257
  • 2
  • 16
  • 28
10

A simple one-liner answer without any regex would be:

str = str.substring(0, str.lastIndexOf(list[i])) + 'finish'
Tim Long
  • 2,039
  • 1
  • 22
  • 25
7

I did not like any of the answers above and came up with the below

function isString(variable) { 
    return typeof (variable) === 'string'; 
}

function replaceLastOccurrenceInString(input, find, replaceWith) {
    if (!isString(input) || !isString(find) || !isString(replaceWith)) {
        // returns input on invalid arguments
        return input;
    }

    const lastIndex = input.lastIndexOf(find);
    if (lastIndex < 0) {
        return input;
    }

    return input.substr(0, lastIndex) + replaceWith + input.substr(lastIndex + find.length);
}

Usage:

const input = 'ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen twenty';
const find = 'teen';
const replaceWith = 'teenhundred';

const output = replaceLastOccurrenceInString(input, find, replaceWith);
console.log(output);

// output: ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteenhundred twenty

Hope that helps!

aumanets
  • 3,703
  • 8
  • 39
  • 59
Pepijn Olivier
  • 927
  • 1
  • 18
  • 32
4

A negative lookahead solution:

str.replace(/(one)(?!.*\1)/, 'finish')

An explanation provided by the site regex101.com,

/(one)(?!.*\1)/

1st Capturing Group (one)

  • one - matches the characters one literally (case sensitive)

Negative Lookahead (?!.*\1)

Assert that the Regex below does not match

  • . matches any character (except for line terminators)
  • * matches the previous token between zero and unlimited times, as many times as possible, giving back as needed (greedy)
  • \1 matches the same text as most recently matched by the 1st capturing group
Michal Miky Jankovský
  • 3,089
  • 1
  • 35
  • 36
pguardiario
  • 53,827
  • 19
  • 119
  • 159
  • 1
    That's probably too smart for most people reading this! :P) – Aurovrata Jan 31 '23 at 11:27
  • Best answer, because others don't take into account that "last occurence" doesn't mean "end of sentence", and fails with e.g "one plus one equals minus one plus three". – OfirD Jun 14 '23 at 13:03
  • Doesn't work in such cases (repeated symbols): `"ugy00000.jpg".replace(/(00)(?!.*\1)/, 55)`. – oleedd Aug 08 '23 at 07:20
3

Couldn't you just reverse the string and replace only the first occurrence of the reversed search pattern? I'm thinking . . .

var list = ['one', 'two', 'three', 'four'];
var str = 'one two, one three, one four, one';
for ( var i = 0; i < list.length; i++)
{
     if (str.endsWith(list[i])
     {
         var reversedHaystack = str.split('').reverse().join('');
         var reversedNeedle = list[i].split('').reverse().join('');

         reversedHaystack = reversedHaystack.replace(reversedNeedle, 'hsinif');
         str = reversedHaystack.split('').reverse().join('');
     }
 }
Fusty
  • 366
  • 3
  • 7
3

If speed is important, use this:

/**
 * Replace last occurrence of a string with another string
 * x - the initial string
 * y - string to replace
 * z - string that will replace
 */
function replaceLast(x, y, z){
    var a = x.split("");
    var length = y.length;
    if(x.lastIndexOf(y) != -1) {
        for(var i = x.lastIndexOf(y); i < x.lastIndexOf(y) + length; i++) {
            if(i == x.lastIndexOf(y)) {
                a[i] = z;
            }
            else {
                delete a[i];
            }
        }
    }

    return a.join("");
}

It's faster than using RegExp.

Pascut
  • 3,291
  • 6
  • 36
  • 64
3

Simple solution would be to use substring method. Since string is ending with list element, we can use string.length and calculate end index for substring without using lastIndexOf method

str = str.substring(0, str.length - list[i].length) + "finish"

Hexer338
  • 41
  • 3
1
function replaceLast(text, searchValue, replaceValue) {
  const lastOccurrenceIndex = text.lastIndexOf(searchValue)
  return `${
      text.slice(0, lastOccurrenceIndex)
    }${
      replaceValue
    }${
      text.slice(lastOccurrenceIndex + searchValue.length)
    }`
}
0

Old fashioned and big code but efficient as possible:

function replaceLast(origin,text){
    textLenght = text.length;
    originLen = origin.length
    if(textLenght == 0)
        return origin;

    start = originLen-textLenght;
    if(start < 0){
        return origin;
    }
    if(start == 0){
        return "";
    }
    for(i = start; i >= 0; i--){
        k = 0;
        while(origin[i+k] == text[k]){
            k++
            if(k == textLenght)
                break;
        }
        if(k == textLenght)
            break;
    }
    //not founded
    if(k != textLenght)
        return origin;

    //founded and i starts on correct and i+k is the first char after
    end = origin.substring(i+k,originLen);
    if(i == 0)
        return end;
    else{
        start = origin.substring(0,i) 
        return (start + end);
    }
}
dario nascimento
  • 587
  • 5
  • 11
0

I would suggest using the replace-last npm package.

var str = 'one two, one three, one four, one';
var result = replaceLast(str, 'one', 'finish');
console.log(result);
<script src="https://unpkg.com/replace-last@latest/replaceLast.js"></script>

This works for string and regex replacements.

danday74
  • 52,471
  • 49
  • 232
  • 283
0
if (string.search(searchstring)>-1) {
    stringnew=((text.split("").reverse().join("")).replace(searchstring, 
    subststring).split("").reverse().join(""))
    }

//with var string= "sdgu()ert(dhfj ) he ) gfrt"
//var searchstring="f"
//var subststring="X"
//var stringnew=""
//results in
//string    :  sdgu()ert(dhfj ) he ) gfrt
//stringnew :  sdgu()ert(dhfj ) he ) gXrt
  • [@pguardiario](https://stackoverflow.com/users/966023/pguardiario)'s answer is by far the most versatile and elegant solution. – Aurovrata Jan 31 '23 at 11:40
-1
str = (str + '?').replace(list[i] + '?', 'finish');
showdev
  • 28,454
  • 37
  • 55
  • 73
zaxvox
  • 11
  • 7
    Normally, an explanation is generally wanted in addition to the answer –  Feb 12 '15 at 23:44