JavaScript: substitui a última ocorrência de texto em uma string

97

Veja meu snippet de código abaixo:

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

Desejo substituir a última ocorrência da palavra um pela palavra terminar na string, o que eu tenho não funcionará porque o método replace apenas substituirá a primeira ocorrência dela. Alguém sabe como posso alterar esse trecho para que substitua apenas a última instância de 'um'

Ruth
fonte

Respostas:

115

Bem, se a string realmente terminar com o padrão, você pode fazer isso:

str = str.replace(new RegExp(list[i] + '$'), 'finish');
Pontudo
fonte
2
Boa ideia. Precisa escapar dessa string primeiro (embora não com seus dados de amostra, concedido), o que não é trivial. :-(
TJ Crowder
@Ruth no prob! @TJ sim, realmente é verdade: Ruth se você acabar com "palavras" que você está procurando que incluem os caracteres especiais usados ​​para expressões regulares, você precisa "escapar" deles, o que como TJ diz é um pouco complicado (embora não impossível).
Pointy
e se você quiser substituir a última ocorrência de string1 dentro de string2?
SuperUberDuper
1
@SuperUberDuper bem, se você quiser combinar algo cercado por caracteres "/". Existem duas maneiras de criar uma instância RegExp: o construtor ( new RegExp(string)) e a sintaxe embutida ( /something/). Você não precisa dos caracteres "/" ao usar o construtor RegExp.
Pointy de
3
@TechLife você teria que aplicar uma transformação à string para "proteger" todos os metacaracteres regex com ` - things like + , * , ? `, Parênteses, colchetes, talvez outras coisas.
Pointy
35

Você pode usar String#lastIndexOf para localizar a última ocorrência da palavra e, em seguida, uma String#substringconcatenação para construir a string de substituição.

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

... ou nesse sentido.

TJ Crowder
fonte
1
Outro exemplo: var nameSplit = item.name.lastIndexOf (","); if (nameSplit! = -1) item.name = item.name.substr (0, nameSplit) + "e" + item.name.substr (nameSplit + 2);
Ben Gotow
22

Eu sei que isso é bobo, mas estou me sentindo criativo esta manhã:

'one two, one three, one four, one'
.split(' ') // array: ["one", "two,", "one", "three,", "one", "four,", "one"]
.reverse() // array: ["one", "four,", "one", "three,", "one", "two,", "one"]
.join(' ') // string: "one four, one three, one two, one"
.replace(/one/, 'finish') // string: "finish four, one three, one two, one"
.split(' ') // array: ["finish", "four,", "one", "three,", "one", "two,", "one"]
.reverse() // array: ["one", "two,", "one", "three,", "one", "four,", "finish"]
.join(' '); // final string: "one two, one three, one four, finish"

Então, realmente, tudo o que você precisa fazer é adicionar esta função ao protótipo String:

String.prototype.replaceLast = function (what, replacement) {
    return this.split(' ').reverse().join(' ').replace(new RegExp(what), replacement).split(' ').reverse().join(' ');
};

Em seguida, execute-o assim: str = str.replaceLast('one', 'finish');

Uma limitação que você deve saber é que, como a função é dividida por espaço, provavelmente você não conseguirá encontrar / substituir nada por um espaço.

Na verdade, agora que penso nisso, você poderia contornar o problema de 'espaço' dividindo com um token vazio.

String.prototype.reverse = function () {
    return this.split('').reverse().join('');
};

String.prototype.replaceLast = function (what, replacement) {
    return this.reverse().replace(new RegExp(what.reverse()), replacement.reverse()).reverse();
};

str = str.replaceLast('one', 'finish');
Matt
fonte
11
@Alexander Uhhh, por favor, não use isso em código de produção ... Ou qualquer código ... Uma simples regex será suficiente.
Ligeiro dia
12

Não tão elegante quanto as respostas de regex acima, mas mais fácil de seguir para os não tão experientes entre nós:

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

Sei que isso é provavelmente mais lento e desperdiçador do que os exemplos de regex, mas acho que pode ser útil como uma ilustração de como as manipulações de strings podem ser feitas. (Também pode ser um pouco condensado, mas, novamente, eu queria que cada etapa fosse clara.)

WilsonCPU
fonte
9

Aqui está um método que usa apenas divisão e união. É um pouco mais legível, então pensei que valia a pena compartilhar:

    String.prototype.replaceLast = function (what, replacement) {
        var pcs = this.split(what);
        var lastPc = pcs.pop();
        return pcs.join(what) + replacement + lastPc;
    };
mr.freeze
fonte
Funciona bem, entretanto adicionará substituição ao início da string se a string não incluir whatnada. por exemplo'foo'.replaceLast('bar'); // 'barfoo'
pie6k
8

Pensei em responder aqui, já que isso apareceu primeiro na minha pesquisa do Google e não há uma resposta (fora da resposta criativa de Matt :)) que substitui genericamente a última ocorrência de uma sequência de caracteres quando o texto a ser substituído pode não estar no final da corda.

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
fonte
4

Uma resposta simples sem qualquer regex seria:

str = str.substr(0, str.lastIndexOf(list[i])) + 'finish'
Tim Long
fonte
2
E se não houver nenhuma ocorrência na string original?
tomazahlin
A resposta mais aceita retorna o mesmo resultado que o meu! Aqui está o resultado do teste:
Tim Long
Meu:> s = s.substr (0, s.lastIndexOf ('d')) + 'terminar' => 'terminar' ... Deles:> s = s.replace (novo RegExp ('d' + '$ '),' terminar ') =>' terminar '
Tim Long
2

Você não poderia simplesmente inverter a string e substituir apenas a primeira ocorrência do padrão de pesquisa invertido? Estou pensando . . .

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
fonte
2

Se a velocidade for importante, use isto:

/**
 * 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("");
}

É mais rápido do que usar RegExp.

Pascut
fonte
1

Código antigo e grande, mas o mais eficiente possível:

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
fonte
1

Uma solução simples seria usar o método substring. Como a string termina com o elemento da lista, podemos usar string.length e calcular o índice final da substring sem usar o lastIndexOfmétodo

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

Hexer338
fonte
1

Eu não gostei de nenhuma das respostas acima e encontrei a seguinte

function replaceLastOccurrenceInString(input, find, replaceWith) {
    if (!input || !find || !replaceWith || !input.length || !find.length || !replaceWith.length) {
        // 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);
}

Uso:

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

Espero que ajude!

Pepijn Olivier
fonte
0

Eu sugeriria usar o pacote replace-last npm.

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>

Isso funciona para substituições de string e regex.

danday74
fonte
-1
str = (str + '?').replace(list[i] + '?', 'finish');
zaxvox
fonte
6
Normalmente, geralmente é necessária uma explicação além da resposta