Powiedzmy, że mam następujący kod:
String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("foo", word1);
story = story.replace("bar", word2);
Po uruchomieniu tego kodu wartość story
will"Once upon a time, there was a foo and a foo."
Podobny problem występuje, gdy wymieniłem je w odwrotnej kolejności:
String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("bar", word2);
story = story.replace("foo", word1);
Wartość story
będzie"Once upon a time, there was a bar and a bar."
Moim celem jest zamienić się story
w "Once upon a time, there was a bar and a foo."
Jak mogłem to osiągnąć?
swap(String s1, String s2, String s3)
która zamienia wszystkie wystąpienias2
zs3
i na odwrót.Odpowiedzi:
Użyj
replaceEach()
metody z Apache Commons StringUtils :źródło
null
jest zdana.Używasz wartości pośredniej (której jeszcze nie ma w zdaniu).
W odpowiedzi na krytykę: jeśli użyjesz wystarczająco dużego, nietypowego ciągu znaków, takiego jak zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddjhodfqsqc, nvùq ^ µù; d & € sdq: d:;), nawet jeśli debata jest nieprawdopodobna, nie jest to nawet prawdopodobne. że użytkownik kiedykolwiek to wprowadzi. Jedynym sposobem, aby dowiedzieć się, czy użytkownik to zrobi, jest znajomość kodu źródłowego i w tym momencie masz zupełnie inny poziom zmartwień.
Tak, może istnieją fantazyjne sposoby wyrażenia regularnego. Wolę coś czytelnego, o czym wiem, że mnie też nie wyrwie.
Powtarzając również doskonałą radę udzieloną przez @Davida Conrada w komentarzach :
źródło
Możesz spróbować czegoś takiego, używając
Matcher#appendReplacement
iMatcher#appendTail
:źródło
foo
,bar
istory
wszyscy mają nieznanych wartości?"foo"
i"bar"
zastępujące, tak jak OP w swoim kodzie, ale ten sam typ podejścia działałby dobrze, nawet jeśli te wartości nie są znane (musiałbyś użyćif
/else if
zamiast aswitch
wwhile
-pętla).Pattern.quote
przyda się, lub\Q
i\E
.(foo)|(bar)
a następnie sprawdzeniem.group(1) != null
, aby uniknąć powtarzania dopasowanych słów.To nie jest łatwy problem. Im więcej masz parametrów zastępujących wyszukiwanie, tym jest to trudniejsze. Masz kilka opcji, rozrzuconych na palecie brzydko-eleganckich, wydajnych-marnotrawnych:
Użyj
StringUtils.replaceEach
z Apache Commons zgodnie z zaleceniami @AlanHay . Jest to dobra opcja, jeśli możesz dodawać nowe zależności w swoim projekcie. Możesz mieć szczęście: zależność może być już uwzględniona w twoim projekcieUżyj tymczasowego symbolu zastępczego, jak sugerował @Jeroen , i wykonaj wymianę w 2 krokach:
Nie jest to dobre podejście z kilku powodów: musi zapewnić, że tagi użyte w pierwszym kroku są naprawdę niepowtarzalne; wykonuje więcej operacji wymiany łańcucha, niż jest to naprawdę konieczne
Budować regex ze wszystkich wzorów i korzystać z metody
Matcher
iStringBuffer
jak sugeruje @arshajii . Nie jest to straszne, ale też nie takie świetne, ponieważ tworzenie wyrażenia regularnego jest trochę hakerskie i wymaga tego,StringBuffer
co wyszło z mody na korzyśćStringBuilder
.Użyj rozwiązania rekurencyjnego zaproponowanego przez @mjolka , dzieląc ciąg na pasujące wzorce i rekurencyjnie na pozostałych segmentach. To dobre rozwiązanie, kompaktowe i dość eleganckie. Jego słabością jest potencjalnie wiele operacji podciągów i konkatenacji oraz ograniczenia rozmiaru stosu, które mają zastosowanie do wszystkich rozwiązań rekurencyjnych
Podziel tekst na słowa i użyj strumieni Java 8, aby elegancko wykonać zamiany, jak sugerował @msandiford , ale oczywiście działa to tylko wtedy, gdy dzielenie na granicach słów jest w porządku, co sprawia, że nie nadaje się jako ogólne rozwiązanie
Oto moja wersja, oparta na pomysłach zapożyczonych z implementacji Apache . Nie jest to ani proste, ani eleganckie, ale działa i powinno być stosunkowo wydajne, bez zbędnych kroków. Krótko mówiąc, działa to w następujący sposób: wielokrotnie znajdź następny pasujący wzorzec wyszukiwania w tekście i użyj a,
StringBuilder
aby zebrać niedopasowane segmenty i zamienniki.Testy jednostkowe:
źródło
Wyszukaj pierwsze słowo do zastąpienia. Jeśli znajduje się w ciągu, powtórz na części ciągu przed wystąpieniem i na części ciągu po wystąpieniu.
W przeciwnym razie przejdź do następnego słowa, które ma zostać zastąpione.
Tak może wyglądać naiwna implementacja
Przykładowe użycie:
Wynik:
Mniej naiwna wersja:
Niestety, Java
String
nie maindexOf(String str, int fromIndex, int toIndex)
metody. PominąłemindexOf
tutaj implementację , ponieważ nie jestem pewien, czy jest poprawna, ale można ją znaleźć na ideone , wraz z niektórymi przybliżonymi czasami różnych rozwiązań zamieszczonych tutaj.źródło
Jedna linijka w Javie 8:
?<=
,?=
): http://www.regular-expressions.info/lookaround.htmlźródło
Oto możliwość strumieni Java 8, która może być interesująca dla niektórych:
Oto przybliżenie tego samego algorytmu w Javie 7:
źródło
Jeśli chcesz zamienić słowa w zdaniu oddzielone białymi znakami, jak pokazano na przykładzie, możesz użyć tego prostego algorytmu.
Jeśli dzielenie w przestrzeni jest nie do przyjęcia, można zastosować ten alternatywny algorytm. Najpierw musisz użyć dłuższego sznurka. Jeśli stringi są foo i fool, musisz najpierw użyć fool, a następnie foo.
źródło
Oto mniej skomplikowana odpowiedź przy użyciu mapy.
I nazywa się metoda
Wynik: awesome jest Raffy, Raffy Raffy jest niesamowity, niesamowity
źródło
replaced.replaceAll("Raffy", "Barney");
za tym sprawi, że będzie legen… poczekaj na to; Dary !!!Jeśli chcesz mieć możliwość obsługi wielu wystąpień ciągów wyszukiwania, które mają zostać zastąpione, możesz to łatwo zrobić, dzieląc ciąg dla każdego wyszukiwanego terminu, a następnie zastępując go. Oto przykład:
źródło
Możesz osiągnąć swój cel za pomocą następującego bloku kodu:
Zastępuje słowa niezależnie od kolejności. Możesz rozszerzyć tę zasadę na metodę użytkową, taką jak:
Które byłyby spożywane jako:
źródło
To działa i jest proste:
Używasz tego w ten sposób:
Uwaga: liczy się to, że ciągi nie zawierają znaku
\ufdd0
, który jest znakiem trwale zarezerwowanym do użytku wewnętrznego przez Unicode (patrz http://www.unicode.org/faq/private_use.html ):Nie sądzę, aby to było konieczne, ale jeśli chcesz być całkowicie bezpieczny, możesz użyć:
źródło
Zamiana tylko jednego wystąpienia
Jeśli na wejściu występuje tylko jedno wystąpienie każdego z wymienialnych ciągów, możesz wykonać następujące czynności:
Przed przystąpieniem do jakiejkolwiek zamiany, uzyskaj indeksy wystąpień słów. Następnie zastępujemy tylko słowo znalezione w tych indeksach, a nie wszystkie wystąpienia. To rozwiązanie wykorzystuje
StringBuilder
i nie wytwarzaString
podobnych produktów pośrednichString.replace()
.Jedna uwaga: jeśli wymienialne słowa mają różne długości, po pierwszym zastąpieniu drugi indeks może się zmienić (jeśli pierwsze słowo występuje przed drugim) dokładnie z różnicą dwóch długości. Zatem wyrównanie drugiego indeksu zapewni, że będzie to działać, nawet jeśli zamieniamy słowa o różnej długości.
Zamiana dowolnej liczby wystąpień
Analogicznie do poprzedniego przypadku najpierw zbierzemy indeksy (wystąpienia) słów, ale w tym przypadku będzie to lista liczb całkowitych dla każdego słowa, a nie tylko jednego
int
. W tym celu użyjemy następującej metody narzędziowej:Używając tego, zastąpimy te słowa innym, zmniejszając indeks (co może wymagać naprzemiennego przełączania między 2 wymiennymi słowami), abyśmy nie musieli nawet poprawiać indeksów po zamianie:
źródło
indexOf
pasuje, może nie mieć takiej samej długości jak szukany ciąg ze względu na idiosynkrazje równoważności ciągów znaków Unicode.String
jest tablicą znaków, a nie tablicą bajtów. Wszystkie metodyString
iStringBuilder
operują na znakach nie na bajtach, które są „wolne od kodowania”. ZatemindexOf
dopasowania mają dokładnie taką samą (znakową) długość jak wyszukiwane ciągi.ä
może być zakodowany jako pojedynczy punkt kodowy lub jakoa
następująca po nim kombinacja¨
. Istnieją również pewne punkty kodowe, które są ignorowane, na przykład łączniki o zerowej szerokości (nie). Nie ma znaczenia, czy łańcuch składa się z bajtów, znaków czy czegokolwiek, ale jakie reguły porównawcze sąindexOf
używane. Może używać prostego porównania jednostka-jednostka przez jednostkę kodu („porządkowa”) lub może implementować równoważność Unicode. Nie wiem, który java wybrał."ab\u00ADc".IndexOf("bc")
zwraca1
w .net dopasowując ciąg dwóch znakówbc
do ciągu trzech znaków."ab\u00ADc".indexOf("bc")
zwraca,-1
które oznacza, że"bc"
nie znaleziono w"ab\u00ADc"
. Tak więc nadalindexOf()
wygląda na to, że w Javie powyższy algorytm działa, dopasowania mają dokładnie taką samą (indexOf()
znakową ) długość jak wyszukiwane ciągi i zgłaszają dopasowania tylko wtedy, gdy są zgodne sekwencje znaków (punkty kodowe).Łatwo jest napisać metodę, aby to zrobić, używając
String.regionMatches
:Testowanie:
Wynik:
Nie jest to od razu oczywiste, ale taka funkcja może nadal zależeć od kolejności, w której określane są zamienniki. Rozważać:
Wynik:
Ale odwróć zamienniki:
Wynik:
Ups! :)
Dlatego czasami warto upewnić się, że szuka się najdłuższego dopasowania (tak jak
strtr
na przykład robi to funkcja PHP ). Ta wersja metody zrobi to:Zwróć uwagę, że powyższe metody uwzględniają wielkość liter. Jeśli potrzebujesz wersji bez rozróżniania wielkości liter, możesz łatwo zmodyfikować powyższe, ponieważ
String.regionMatches
może przyjmowaćignoreCase
parametr.źródło
Jeśli nie chcesz żadnych zależności, możesz po prostu użyć tablicy, która pozwala tylko na jednorazową zmianę. Nie jest to najbardziej wydajne rozwiązanie, ale powinno działać.
Wtedy to powinno zadziałać.
źródło
Na wejściu wykonujesz wiele operacji wyszukiwania-zamiany. Spowoduje to niepożądane wyniki, gdy ciągi zastępcze zawierają ciągi wyszukiwania. Rozważmy przykład foo-> bar, bar-foo, oto wyniki dla każdej iteracji:
Musisz wykonać zamianę w jednej iteracji bez cofania się. Rozwiązanie siłowe jest następujące:
Taka funkcja
String.indexOfAny(String[]) -> int[]{index, whichString}
byłaby przydatna. Oto przykład (nie najbardziej wydajny):Niektóre testy:
Demo na IDEONE
Demo na IDEONE, alternatywny kod
źródło
Zawsze możesz zastąpić go słowem, co do którego jesteś pewien, że nie pojawi się nigdzie indziej w ciągu, a następnie wykonaj drugą zamianę później:
Zauważ, że to nie zadziała, jeśli
"StringYouAreSureWillNeverOccur"
tak się stanie.źródło
Rozważ użycie StringBuilder
Następnie zapisz indeks, od którego powinien zaczynać się każdy ciąg. Jeśli używasz znaku zastępczego w każdej pozycji, usuń go i wstaw ciąg użytkownika. Następnie można odwzorować pozycję końcową, dodając długość ciągu do pozycji początkowej.
źródło
Jedyne, czym mogę się podzielić, to moja własna metoda.
Możesz użyć tymczasowego
String temp = "<?>";
lubString.Format();
To jest mój przykładowy kod utworzony w aplikacji konsoli za pośrednictwem do# - „Tylko pomysł, brak dokładnej odpowiedzi” .
Możesz też użyć rozszerzenia
String.Format();
Wynik:
time upon a Once, there was a bar and a foo.
źródło
temp
z"_"
na<?>
. Ale w razie potrzeby może dodać kolejny parametr do metody, która zmieni temp. - "lepiej jest to proste, prawda?"Oto moja wersja oparta na słowach:
źródło
Trochę trudny sposób, ale musisz jeszcze sprawdzić.
1. przekształcić ciąg znaków w tablicę znaków
2.loop na temp i wymienić
foo
zbar
ibar
zefoo
jak nie ma szans na uzyskanie wymienną ciąg ponownie.źródło
Cóż, krótsza odpowiedź brzmi ...
źródło
Korzystając z odpowiedzi znalezionej tutaj , możesz znaleźć wszystkie wystąpienia ciągów, które chcesz zastąpić.
Na przykład uruchamiasz kod w powyższej odpowiedzi SO. Utwórz dwie tabele indeksów (powiedzmy, że bar i foo nie pojawiają się tylko raz w ciągu) i możesz pracować z tymi tabelami nad zastąpieniem ich w ciągu.
Teraz do zamiany w określonych lokalizacjach indeksu możesz użyć:
Natomiast
pos
jest to indeks, w którym zaczynają się twoje ciągi (z tabel indeksów, które cytowałem powyżej). Powiedzmy, że utworzyłeś dwie tabele indeksów dla każdej z nich. Nazwijmy jeindexBar
iindexFoo
.Teraz zastępując je, możesz po prostu uruchomić dwie pętle, po jednej dla każdej wymiany, którą chcesz wykonać.
Podobnie kolejna pętla dla
indexFoo
.To może nie być tak wydajne jak inne odpowiedzi tutaj, ale jest łatwiejsze do zrozumienia niż Mapy lub inne rzeczy.
Dałoby to zawsze pożądany wynik i wiele możliwych wystąpień każdego ciągu. Tak długo, jak przechowujesz indeks każdego wystąpienia.
Również ta odpowiedź nie wymaga rekursji ani żadnych zewnętrznych zależności. Jeśli chodzi o złożoność, to prawdopodobnie jest to O (n do kwadratu), podczas gdy n jest sumą wystąpień obu słów.
źródło
Opracowałem ten kod, który rozwiąże problem:
W głównym zastosowaniu
change(story,word2,word1).
źródło
źródło