Допустим, у меня есть следующий код:
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);
После запуска этого кода значение story
будет"Once upon a time, there was a foo and a foo."
Аналогичная проблема возникает, если я заменил их в обратном порядке:
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);
Значение story
будет"Once upon a time, there was a bar and a bar."
Моя цель - превратиться story
в "Once upon a time, there was a bar and a foo."
Как я могу это сделать?
swap(String s1, String s2, String s3)
которая меняет все вхожденияs2
сs3
, и наоборот.Ответы:
Используйте
replaceEach()
метод из Apache Commons StringUtils :источник
null
его принимают.Вы используете промежуточное значение (которого еще нет в предложении).
В ответ на критику: если вы используете достаточно большую необычную строку, такую как zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddjhodfqsqc, то nvùq ^ µù; d & € sdq: d:;) даже не спорят, даже если и не склонны даже использовать это, даже если и не будете спорить, даже если вы даже не будете это использовать что пользователь когда-либо введет это. Единственный способ узнать, будет ли это делать пользователь, зная исходный код, и в этот момент у вас будет совсем другой уровень беспокойства.
Да, может быть, есть причудливые способы регулярных выражений. Я предпочитаю что-то читаемое, что я знаю, не произойдет и на меня.
Также повторяя превосходный совет, данный @David Conrad в комментариях :
источник
Вы можете попробовать что-то вроде этого, используя
Matcher#appendReplacement
иMatcher#appendTail
:источник
foo
,bar
иstory
все имеют неизвестные значения?"foo"
и"bar"
строку замены как OP имел в своем коде, но тот же тип подхода будет работать хорошо , даже если эти значения не известны (вы должны использоватьif
/else if
вместоswitch
внутриwhile
-loop).Pattern.quote
пригодится, или\Q
и\E
.(foo)|(bar)
а затем проверитьm.group(1) != null
, чтобы избежать повторения слов, чтобы соответствовать.Это не простая проблема. И чем больше у вас параметров поиска-замены, тем сложнее становится. У вас есть несколько вариантов, разбросанных по палитре некрасиво-элегантно, эффективно-расточительно:
Используйте
StringUtils.replaceEach
от Apache Commons, как рекомендуется @AlanHay . Это хороший вариант, если вы можете добавлять новые зависимости в ваш проект. Возможно, вам повезет: зависимость может быть включена уже в ваш проектИспользуйте временный заполнитель, как предложено @Jeroen , и выполните замену в 2 этапа:
Это не очень хороший подход по нескольким причинам: необходимо убедиться, что теги, используемые на первом этапе, действительно уникальны; он выполняет больше операций замены строк, чем необходимо
Создайте регулярное выражение из всех шаблонов и используйте метод с
Matcher
иStringBuffer
как предложено @arshajii . Это не страшно, но и не так уж и здорово, так как построение регулярного выражения является своего рода хакерским, и оно включаетStringBuffer
в себя то, что давно вышло из моды в пользуStringBuilder
.Используйте рекурсивное решение, предложенное @mjolka , разделив строку на совпавшие шаблоны и вернувшись к оставшимся сегментам. Это прекрасное решение, компактное и довольно элегантное. Его слабость заключается в потенциально большом количестве подстрок и операций конкатенации, а также в ограничениях размера стека, применимых ко всем рекурсивным решениям.
Разделите текст на слова и используйте потоки Java 8 для элегантного выполнения замен, как предложено @msandiford , но, конечно, это работает, только если вы хорошо разбиваете границы слов, что делает его непригодным в качестве общего решения
Вот моя версия, основанная на идеях, заимствованных из реализации Apache . Это не просто и не элегантно, но работает и должно быть относительно эффективным, без лишних шагов. В двух словах, это работает так: многократно находите следующий соответствующий шаблон поиска в тексте и используйте a
StringBuilder
для накопления несопоставленных сегментов и замен.Модульные тесты:
источник
Найдите первое слово для замены. Если он находится в строке, выполните рекурсивный анализ в части строки до вхождения и в части строки после вхождения.
В противном случае перейдите к следующему слову, подлежащему замене.
Наивная реализация может выглядеть так
Пример использования:
Вывод:
Менее наивная версия:
К сожалению, у Java
String
нетindexOf(String str, int fromIndex, int toIndex)
метода. Я пропустил реализациюindexOf
здесь, так как не уверен, что это правильно, но его можно найти на ideone , а также некоторые грубые временные рамки различных решений, размещенных здесь.источник
Однострочник в Java 8:
?<=
,?=
): http://www.regular-expressions.info/lookaround.htmlисточник
Вот возможность потоков Java 8, которая может быть интересна для некоторых:
Вот пример того же алгоритма в Java 7:
источник
Если вы хотите заменить слова в предложении, разделенные пробелами, как показано в вашем примере, вы можете использовать этот простой алгоритм.
Если расщепление по пространству неприемлемо, можно следовать этому альтернативному алгоритму. Вы должны использовать более длинную строку в первую очередь. Если строки foo и fool, вам нужно сначала использовать fool, а затем foo.
источник
Вот менее сложный ответ с использованием карты.
И метод называется
Вывод: круто Раффи, Раффи Раффи круто
источник
replaced.replaceAll("Raffy", "Barney");
за этим сделает его леген ... подождать; Дарий !!!Если вы хотите иметь возможность обрабатывать несколько вхождений искомых строк, которые вы хотите заменить, вы можете легко это сделать, разделив строку по каждому поисковому запросу, а затем заменив ее. Вот пример:
источник
Вы можете достичь своей цели с помощью следующего блока кода:
Он заменяет слова независимо от порядка. Вы можете расширить этот принцип в служебный метод, например:
Который будет потребляться как:
источник
Это работает и просто:
Вы используете это так:
Примечание: это рассчитывает на струнном , не содержащий символ
\ufdd0
, который представляет собой символ , постоянно зарезервирован для внутреннего использования Unicode (см http://www.unicode.org/faq/private_use.html ):Я не думаю, что это необходимо, но если вы хотите быть в полной безопасности, вы можете использовать:
источник
Обмен только одним вхождением
Если во входных данных есть только одно вхождение каждой из заменяемых строк, вы можете сделать следующее:
Прежде чем приступить к любой замене, получите индексы вхождений слов. После этого мы заменяем только слова, найденные по этим индексам, а не все вхождения. Это решение использует
StringBuilder
и не производит промежуточныеString
сортаString.replace()
.Следует отметить одну вещь: если заменяемые слова имеют разную длину, после первой замены второй индекс может измениться (если 1-е слово встречается до 2-го) точно с разницей в 2 длины. Таким образом, выравнивание второго индекса гарантирует, что это работает, даже если мы меняем слова различной длины.
Обмен произвольным числом вхождений
По аналогии с предыдущим случаем мы сначала соберем индексы (вхождения) слов, но в этом случае это будет список целых чисел для каждого слова, а не только для одного
int
. Для этого мы будем использовать следующий служебный метод:И используя это, мы заменим слова другим, уменьшив индекс (что может потребовать чередования двух заменяемых слов), так что нам даже не придется исправлять индексы после замены:
источник
indexOf
совпадающая подстрока может не иметь такую же длину, что и строка поиска, благодаря особенностям эквивалентности строк Юникода.String
является массивом символов, а не байтовым массивом. Все методыString
иStringBuilder
работают с символами, а не с байтами, которые не кодируются. Таким образом,indexOf
совпадения имеют точно такую же (символьную) длину, что и строки поиска.ä
может быть закодирован как одна кодовая точка или какa
последующее объединение¨
. Есть также некоторые кодовые точки, которые игнорируются, такие как соединения нулевой ширины (не). Не имеет значения, состоит ли строка из байтов, символов и т. Д., Но какие правила сравненияindexOf
используются. Он может использовать просто кодовую единицу путем сравнения кодовых единиц («Порядковый») или может реализовывать эквивалентность Юникода. Я не знаю, какую именно Java выбрал."ab\u00ADc".IndexOf("bc")
возвращает1
в .net соответствие строки из двух символов строкеbc
из трех символов."ab\u00ADc".indexOf("bc")
возвращается,-1
значит"bc"
не было найдено в"ab\u00ADc"
. Таким образом, все еще стоит отметить, что в Java работает вышеупомянутый алгоритм,indexOf()
совпадения имеют точно такую же (символьную) длину, что и строки поиска, иindexOf()
сообщают о совпадениях только в том случае, если совпадают символы последовательности (кодовые точки).Легко написать метод для этого, используя
String.regionMatches
:Тестирование:
Вывод:
Это не сразу очевидно, но такая функция все еще может зависеть от порядка, в котором указаны замены. Рассматривать:
Вывод:
Но поменяйте местами замены:
Вывод:
К сожалению! :)
Поэтому иногда полезно убедиться, что найдено наибольшее совпадение (как
strtr
, например , функция PHP ). Эта версия метода сделает это:Обратите внимание, что вышеупомянутые методы чувствительны к регистру. Если вам нужна версия без учета регистра, легко изменить вышеприведенное, поскольку она
String.regionMatches
может приниматьignoreCase
параметр.источник
Если вы не хотите никаких зависимостей, вы можете просто использовать массив, который допускает только одноразовое изменение. Это не самое эффективное решение, но оно должно работать.
Тогда это будет работать.
источник
Вы выполняете несколько операций поиска-замены на входе. Это приведет к нежелательным результатам, когда строки замены содержат строки поиска. Рассмотрим пример foo-> bar, bar-foo, вот результаты для каждой итерации:
Вы должны выполнить замену за одну итерацию, не возвращаясь. Решение о грубой силе следующее:
Такая функция, как
String.indexOfAny(String[]) -> int[]{index, whichString}
было бы полезно. Вот пример (не самый эффективный):Некоторые тесты:
Демо на IDEONE
Демо на IDEONE, альтернативный код
источник
Вы всегда можете заменить его словом, которое наверняка больше нигде не появится в строке, а затем выполнить вторую замену позже:
Обратите внимание, что это не будет работать правильно, если
"StringYouAreSureWillNeverOccur"
это произойдет.источник
Рассмотрите возможность использования StringBuilder
Затем сохраните индекс, где каждая строка должна начинаться. Если в каждой позиции используется символ-заполнитель, удалите его и вставьте строку пользователя. Затем вы можете отобразить конечную позицию, добавив длину строки к начальной позиции.
источник
То, что я могу только поделиться, это мой собственный метод.
Вы можете использовать временный
String temp = "<?>";
илиString.Format();
Это мой пример кода, созданного в консольном приложении через C # - «Только идея, а не точный ответ» .
Или вы также можете использовать
String.Format();
Вывод:
time upon a Once, there was a bar and a foo.
источник
temp
с"_"
на<?>
. Но при необходимости он может добавить в метод еще один параметр, который изменит темп. - "Лучше быть проще, не так ли?"Вот моя версия, которая основана на словах:
источник
Немного сложно, но вам нужно сделать еще несколько проверок.
1. преобразовать строку в массив символов
2. зацикливайтесь на temp и заменяйте
foo
наbar
иbar
с, такfoo
как нет шансов получить заменяемую строку снова.источник
Ну, короче ответ ...
источник
Используя найденный здесь ответ, вы можете найти все вхождения строк, которыми хотите заменить.
Так, например, вы запускаете код в ответе выше SO. Создайте две таблицы индексов (скажем, bar и foo не появляются в вашей строке только один раз), и вы можете работать с этими таблицами, заменяя их в вашей строке.
Теперь для замены в определенных местах индекса вы можете использовать:
Принимая во внимание,
pos
что это индекс, с которого начинаются ваши строки (из таблиц индексов, которые я цитировал выше). Допустим, вы создали две таблицы индексов для каждой. Давайте назовем ихindexBar
иindexFoo
.Теперь, заменяя их, вы можете просто запустить два цикла, по одному для каждой замены, которую вы хотите сделать.
Точно так же еще один цикл для
indexFoo
.Это может быть не так эффективно, как другие ответы здесь, но это проще понять, чем Карты или другие вещи.
Это всегда даст вам желаемый результат и для нескольких возможных вхождений каждой строки. Пока вы храните индекс каждого вхождения.
Также этот ответ не нуждается ни в рекурсии, ни в каких-либо внешних зависимостях. Что касается сложности, то, вероятно, это O (n в квадрате), тогда как n - сумма совпадений обоих слов.
источник
Я разработал этот код, чтобы решить проблему:
В основном использовании
change(story,word2,word1).
источник
источник