JavaScript: zamień ostatnie wystąpienie tekstu w ciągu

101

Zobacz mój fragment kodu poniżej:

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

Chcę zastąpić ostatnie wystąpienie słowa jeden słowem finish w ciągu, to co mam nie zadziała, ponieważ metoda replace zamieni tylko pierwsze jego wystąpienie. Czy ktoś wie, jak mogę zmienić ten fragment, tak aby zastępował tylko ostatnie wystąpienie „jeden”

Litość
źródło

Odpowiedzi:

117

Cóż, jeśli ciąg naprawdę kończy się wzorem, możesz zrobić to:

str = str.replace(new RegExp(list[i] + '$'), 'finish');
Spiczasty
źródło
2
Dobry pomysł. Trzeba najpierw uciec od tego ciągu (choć nie z jej przykładowymi danymi, prawda), co jest nietrywialne. :-(
TJ Crowder
@Ruth no prob! @TJ tak, rzeczywiście to prawda: Ruth, jeśli skończysz ze „słowami”, których szukasz, które zawierają znaki specjalne używane w wyrażeniach regularnych, musisz „uciec” przed tymi, co, jak mówi TJ, jest trochę trudne (choć nie niemożliwe).
Pointy
co jeśli chcesz zamienić ostatnie wystąpienie string1 wewnątrz string2?
SuperUberDuper
1
@SuperUberDuper dobrze, jeśli chcesz dopasować coś otoczonego znakami „/”. Istnieją dwa sposoby tworzenia instancji RegExp: konstruktor ( new RegExp(string)) i wbudowana składnia ( /something/). Nie potrzebujesz znaków „/”, gdy używasz konstruktora RegExp.
Pointy
3
@TechLife musiałbyś zastosować transformację do ciągu, aby "chronić" wszystkie metaznaki wyrażenia regularnego za pomocą ` - things like + , * , ? `, Nawiasów, nawiasów kwadratowych, może innych rzeczy.
Pointy
36

Możesz użyć, String#lastIndexOfaby znaleźć ostatnie wystąpienie słowa, a następnie String#substringi połączyć w celu zbudowania ciągu zastępczego.

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

... lub wzdłuż tych linii.

TJ Crowder
źródło
1
Inny przykład: var nameSplit = item.name.lastIndexOf (","); if (nameSplit! = -1) item.name = item.name.substr (0, nameSplit) + "i" + item.name.substr (nameSplit + 2);
Ben Gotow,
23

Wiem, że to głupie, ale dziś rano czuję się kreatywny:

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

Więc tak naprawdę wszystko, co musiałbyś zrobić, to dodać tę funkcję do prototypu String:

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

Następnie uruchom to tak: str = str.replaceLast('one', 'finish');

Jedynym ograniczeniem, które powinieneś wiedzieć, jest to, że ponieważ funkcja jest dzielona przez spację, prawdopodobnie nie możesz znaleźć / zastąpić niczego spacją.

Właściwie, teraz, kiedy o tym myślę, możesz obejść problem „przestrzeni”, rozdzielając go pustym tokenem.

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
źródło
11
@Alexander Uhhh, proszę, nie używaj tego w kodzie produkcyjnym ... Ani żadnego kodu ... Wystarczy proste wyrażenie regularne.
Lekko
12

Nie tak eleganckie, jak powyższe wyrażenie regularne, ale łatwiejsze do zrozumienia dla mniej doświadczonych spośród nas:

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

Zdaję sobie sprawę, że jest to prawdopodobnie wolniejsze i bardziej marnotrawne niż przykłady wyrażeń regularnych, ale myślę, że może być pomocne jako ilustracja tego, jak można wykonać manipulacje na ciągach. (Można to też trochę skondensować, ale znowu chciałem, aby każdy krok był jasny.)

WilsonCPU
źródło
9

Oto metoda, która wykorzystuje tylko dzielenie i łączenie. Jest trochę bardziej czytelny, więc pomyślałem, że warto się nim podzielić:

    String.prototype.replaceLast = function (what, replacement) {
        var pcs = this.split(what);
        var lastPc = pcs.pop();
        return pcs.join(what) + replacement + lastPc;
    };
mr.freeze
źródło
Działa dobrze, ale doda zastąpienie do początku ciągu, jeśli łańcuch whatw ogóle nie zawiera . np.'foo'.replaceLast('bar'); // 'barfoo'
pie6k
8

Pomyślałem, że odpowiem tutaj, ponieważ pojawiło się to jako pierwsze w mojej wyszukiwarce Google i nie ma odpowiedzi (poza twórczą odpowiedzią Matta :)), która generalnie zastępuje ostatnie wystąpienie ciągu znaków, gdy tekst do zastąpienia może nie znajdować się na końcu sznurka.

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(',', ';'));
Irvinga
źródło
5

Prosta odpowiedź bez wyrażenia regularnego brzmiałaby:

str = str.substr(0, str.lastIndexOf(list[i])) + 'finish'
Tim Long
źródło
2
A co, jeśli nie ma wystąpienia w oryginalnym ciągu?
tomazahlin
Najbardziej akceptowana odpowiedź daje taki sam wynik jak mój! Oto wynik testu:
Tim Long
Mine:> s = s.substr (0, s.lastIndexOf ('d')) + 'finish' => 'finish' ... Ich:> s = s.replace (new RegExp ('d' + '$ '),' finish ') =>' finish '
Tim Long
2

Czy nie mógłbyś po prostu odwrócić łańcucha i zamienić tylko pierwszego wystąpienia odwróconego wzorca wyszukiwania? Myślę . . .

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('');
     }
 }
Stęchły
źródło
2

Jeśli ważna jest prędkość, użyj tego:

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

To szybsze niż użycie RegExp.

Pascut
źródło
2

Prostym rozwiązaniem byłoby użycie metody podciągowej. Ponieważ łańcuch kończy się elementem listy, możemy użyć string.length i obliczyć indeks końcowy dla podłańcucha bez użycia lastIndexOfmetody

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

Hexer338
źródło
1

Staroświecki i duży kod, ale wydajny, jak to tylko możliwe:

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
źródło
1

Nie podobała mi się żadna z powyższych odpowiedzi i wymyśliłem poniższe

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

Stosowanie:

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

Mam nadzieję, że to pomoże!

Pepijn Olivier
źródło
0

Sugerowałbym użycie pakietu 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>

Działa to w przypadku zamiany ciągów i wyrażeń regularnych.

danday74
źródło
-1
str = (str + '?').replace(list[i] + '?', 'finish');
zaxvox
źródło
6
Zwykle oprócz odpowiedzi