154

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'

15 Answers 15

173

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

str = str.replace(new RegExp(list[i] + '$'), 'finish');
Sign up to request clarification or add additional context in comments.

12 Comments

Good idea. Need to escape that string first (though not with her sample data, granted), which is non-trivial. :-(
@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).
what if you want to replace the last occurance of string1 inside string2?
@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.
@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.
|
52

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.

1 Comment

Another example: var nameSplit = item.name.lastIndexOf(", "); if (nameSplit != -1) item.name = item.name.substr(0, nameSplit) + " and "+ item.name.substr(nameSplit + 2);
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.)

Comments

16

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;
    };

2 Comments

It works well, however it'll add replacement to start of the string if string doesnt include what at all. eg 'foo'.replaceLast('bar'); // 'barfoo'
Please avoid prototype pollution.
16

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

str = str.substring(0, str.lastIndexOf(list[i])) + 'finish'

4 Comments

What if there is no occurence in the original string?
The most accepted answer returns the same result as mine! Here is the test result:
Mine: > s = s.substr(0, s.lastIndexOf('d')) + 'finish' => 'finish' ... Theirs: > s = s.replace(new RegExp('d' + '$'), 'finish') => 'finish'
A good answer, but substr() is deprecated. I think you may just be able to replace substr() with substring().
12

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

6 Comments

That's probably too smart for most people reading this! :P)
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".
Doesn't work in such cases (repeated symbols): "ugy00000.jpg".replace(/(00)(?!.*\1)/, 55).
This works neatly to prevent orphan words on new lines, too -- someText.replace(/(\s)(?!.*\1)/, ' ') (there is a non-breaking space character in the replacement string). By inserting that, any text will end in "last&nbsp;word.", ensuring "last" and "word." will be placed on the same line.
@MoritzFriedrich - CSS can prevent orphan words in new line: text-wrap: pretty
|
12

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(',', ';'));

2 Comments

if (index < 1) should be if (index < 0).
Good catch, @Qwertie. My code was correct before it was edited. I rolled it back.
8

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!

Comments

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('');
     }
 }

Comments

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.

Comments

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"

Comments

2
function replaceLast(text, searchValue, replaceValue) {
  const lastOccurrenceIndex = text.lastIndexOf(searchValue)
  return `${
      text.slice(0, lastOccurrenceIndex)
    }${
      replaceValue
    }${
      text.slice(lastOccurrenceIndex + searchValue.length)
    }`
}

Comments

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

Comments

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

1 Comment

@pguardiario's answer is by far the most versatile and elegant solution.
-1
str = (str + '?').replace(list[i] + '?', 'finish');

1 Comment

Normally, an explanation is generally wanted in addition to the answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.