JavaScript: reemplaza la última aparición de texto en una cadena

97

Vea mi fragmento de código a continuación:

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

Quiero reemplazar la última aparición de la palabra uno con la palabra final en la cadena, lo que tengo no funcionará porque el método de reemplazo solo reemplazará la primera aparición. ¿Alguien sabe cómo puedo modificar ese fragmento para que solo reemplace la última instancia de 'uno'

Piedad
fuente

Respuestas:

115

Bueno, si la cadena realmente termina con el patrón, puedes hacer esto:

str = str.replace(new RegExp(list[i] + '$'), 'finish');
Puntiagudo
fuente
2
Buena idea. Necesita escapar de esa cadena primero (aunque no con sus datos de muestra, concedido), lo cual no es trivial. :-(
TJ Crowder
@Ruth no hay problema! @TJ sí, de hecho eso es cierto: Ruth, si terminas con "palabras" que estás buscando que incluyan los caracteres especiales que se usan para las expresiones regulares, deberías "escapar" de ellas, lo cual, como dice TJ, es un poco complicado. (aunque no imposible).
Puntiagudo
¿Qué sucede si desea reemplazar la última aparición de string1 dentro de string2?
SuperUberDuper
1
@SuperUberDuper bueno, lo es si quieres hacer coincidir algo rodeado de caracteres "/". Hay dos formas de crear una instancia de RegExp: el constructor ( new RegExp(string)) y la sintaxis en línea ( /something/). No necesita los caracteres "/" cuando usa el constructor RegExp.
Puntiagudo
3
@TechLife tendrías que aplicar una transformación a la cadena para "proteger" todos los metacaracteres regex con ` - things like + , * , ? `, Paréntesis, corchetes, tal vez otras cosas.
Puntiagudo
35

Puede usar String#lastIndexOfpara encontrar la última aparición de la palabra y luego una String#substringconcatenación para construir la cadena de reemplazo.

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

... o en ese sentido.

TJ Crowder
fuente
1
Otro ejemplo: var nameSplit = item.name.lastIndexOf (","); if (nameSplit! = -1) item.name = item.name.substr (0, nameSplit) + "y" + item.name.substr (nameSplit + 2);
Ben Gotow
22

Sé que esto es una tontería, pero me siento creativo esta mañana:

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

Entonces, realmente, todo lo que necesita hacer es agregar esta función al prototipo String:

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

Entonces ejecútelo así: str = str.replaceLast('one', 'finish');

Una limitación que debe saber es que, dado que la función se divide por espacio, probablemente no pueda encontrar / reemplazar nada con un espacio.

En realidad, ahora que lo pienso, podrías solucionar el problema del 'espacio' dividiéndote con un token vacío.

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');
Mate
fuente
11
@Alexander Uhhh, por favor no use esto en el código de producción ... O cualquier código ... Una simple expresión regular será suficiente.
Ligero
12

No es tan elegante como las respuestas de expresiones regulares anteriores, pero es más fácil de seguir para los que no son tan inteligentes entre nosotros:

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

Me doy cuenta de que esto es probablemente más lento y más derrochador que los ejemplos de expresiones regulares, pero creo que podría ser útil como ilustración de cómo se pueden realizar las manipulaciones de cadenas. (También se puede condensar un poco, pero nuevamente, quería que cada paso fuera claro).

WilsonCPU
fuente
9

Este es un método que solo usa dividir y unir. Es un poco más legible, así que pensé que valía la pena compartirlo:

    String.prototype.replaceLast = function (what, replacement) {
        var pcs = this.split(what);
        var lastPc = pcs.pop();
        return pcs.join(what) + replacement + lastPc;
    };
señor freeze
fuente
Funciona bien, sin embargo, agregará un reemplazo al inicio de la cadena si la cadena no se incluye whaten absoluto. p'foo'.replaceLast('bar'); // 'barfoo'
pie6k
8

Pensé que respondería aquí ya que esto apareció primero en mi búsqueda de Google y no hay respuesta (fuera de la respuesta creativa de Matt :)) que reemplaza genéricamente la última aparición de una cadena de caracteres cuando el texto a reemplazar podría no estar al final de la cuerda.

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

Una respuesta simple sin ninguna expresión regular sería:

str = str.substr(0, str.lastIndexOf(list[i])) + 'finish'
Tim Long
fuente
2
¿Qué pasa si no hay ninguna ocurrencia en la cadena original?
Tomazahlin
¡La respuesta más aceptada devuelve el mismo resultado que la mía! Aquí está el resultado de la prueba:
Tim Long
Mío:> s = s.substr (0, s.lastIndexOf ('d')) + 'finish' => 'finish' ... El suyo:> s = s.replace (new RegExp ('d' + '$ '),' finish ') =>' finish '
Tim Long
2

¿No podría simplemente invertir la cadena y reemplazar solo la primera aparición del patrón de búsqueda invertido? Estoy 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('');
     }
 }
Rancio
fuente
2

Si la velocidad es importante, use esto:

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

Es más rápido que usar RegExp.

Pascut
fuente
1

Código antiguo y grande, pero lo más eficiente posible:

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

La solución simple sería utilizar el método de subcadena. Dado que la cadena termina con el elemento de la lista, podemos usar string.length y calcular el índice final para la subcadena sin usar el lastIndexOfmétodo

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

Hexer338
fuente
1

No me gustó ninguna de las respuestas anteriores y se me ocurrió la siguiente

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 ayude!

Pepijn Olivier
fuente
0

Sugeriría usar el paquete 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>

Esto funciona para reemplazos de cadenas y expresiones regulares.

danday74
fuente
-1
str = (str + '?').replace(list[i] + '?', 'finish');
zaxvox
fuente
6
Normalmente, generalmente se desea una explicación además de la respuesta