В проекте у нас есть текстовые файлы, которые выглядят так:
mv A, R3
mv R2, B
mv R1, R3
mv B, R4
add A, R1
add B, R1
add R1, R2
add R3, R3
add R21, X
add R12, Y
mv X, R2
Мне нужно заменить строки в соответствии со следующим, но я ищу для более общего решения.
R1 => R2
R2 => R3
R3 => R1
R12 => R21
R21 => R12
Я знаю, что могу сделать это в Perl, функция replace () в следующем коде, но реальное приложение написано на Java, поэтому решение должно быть в Java тоже.
#!/usr/bin/perl
use strict;
use warnings;
use File::Slurp qw(read_file write_file);
my %map = (
R1 => 'R2',
R2 => 'R3',
R3 => 'R1',
R12 => 'R21',
R21 => 'R12',
);
replace(\%map, \@ARGV);
sub replace {
my ($map, $files) = @_;
# Create R12|R21|R1|R2|R3
# making sure R12 is before R1
my $regex = join "|",
sort { length($b) <=> length($a) }
keys %$map;
my $ts = time;
foreach my $file (@$files) {
my $data = read_file($file);
$data =~ s/\b($regex)\b/$map{$1}/g;
rename $file, "$file.$ts"; # backup with current timestamp
write_file( $file, $data);
}
}
Мы будем признательны за вашу помощь в реализации Java.
поэтому R12 будет сопоставлен до R1 (иначе R12 никогда не будет сопоставлен), комментарий теперь также добавлен в код.
Сортировка их на самом деле не является решением; если текст содержит «R13», он будет заменен на «R23». Вы хотите, чтобы регулярное выражение соответствовало тому, что вы ему указываете, и ничего больше. Границы слов будут делать это в этом случае: / \ b (R1 | R2 | R3 | R12 | R21) \ b /
вы правы, добавление \ b вокруг регулярного выражения гарантирует, что R122, который следует оставить в покое, не соответствует R12 и изменен на R212




Вы можете использовать HashMap:
Map<String, String> map = new HashMap<String, String>();
map.put("R1", "R2");
map.put("R2", "R3");
for(String key: map.keySet()) {
str.replaceAll(key, map.get(key));
}
replaceAll также обрабатывает регулярные выражения.
Обновлено: Вышеупомянутое решение, как многие отмечали, не работает, потому что оно не обрабатывает циклические замены. Итак, это мой второй подход:
public class Replacement {
private String newS;
private String old;
public Replacement(String old, String newS) {
this.newS = newS;
this.old = old;
}
public String getOld() {
return old;
}
public String getNew() {
return newS;
}
}
SortedMap<Integer, Replacement> map = new TreeMap<Integer, Replacement>();
map.put(new Integer(1), new Replacement("R2", "R3"));
map.put(new Integer(2), new Replacement("R1", "R2"));
for(Integer key: map.keySet()) {
str.replaceAll(map.get(key).getOld(), map.get(key).getNew());
}
Это работает при условии, что вы правильно заказываете замену и защищаете себя от циклической замены. Некоторые замены невозможны:
R1 -> R2
R2 -> R3
R3 -> R1
Для этого вы должны использовать некоторые временные переменные:
R1 -> R@1
R2 -> R@3
R3 -> R1
R@(\d{1}) -> R\1
Вы можете написать библиотеку, которая сделает все это за вас.
Поскольку replaceAll () обрабатывает регулярные выражения, цитирование его аргументов, вероятно, является хорошей идеей.
Неужели это решение не сработает? Ваша первая итерация заменит «R1» на «R2» повсюду, затем ваша вторая итерация заменит «R2» на «R3» повсюду, включая значения, которые изначально были "R1" - в результате R1, R2 и R3 будут * все будет отображаться как R3 к концу.
И поскольку на карте нет гарантии заказа, этот может или не может случиться, что делает его еще более интересным ...
@ Адриан: Я с вами - я сомневаюсь, что это работа с циклическими заменами в вопросе.
Вы можете получить упорядоченную карту, переключившись на TreeMap.
Другая проблема: строки Java неизменяемы, поэтому str.replaceAll () создает новую строку, но вы ее отбрасываете.
Преимущество решения perl состоит в том, что все строки заменяются за один раз, что-то вроде «транзакционной». Если у вас нет такой же опции в Java (и я не могу придумать, как это сделать), вам нужно быть осторожным, заменяя R1 => R2, затем R2 => R3. В этом случае и R1, и R2 заменяются на R3.
На самом деле мне приходилось использовать такой алгоритм несколько раз за последние две недели. Итак, это второй по многословности язык в мире ...
import java.util.HashMap;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
/*
R1 => R2
R2 => R3
R3 => R1
R12 => R21
R21 => R12
*/
String inputString
= "mv A, R3\n"
+ "mv R2, B\n"
+ "mv R1, R3\n"
+ "mv B, R4\n"
+ "add A, R1\n"
+ "add B, R1\n"
+ "add R1, R2\n"
+ "add R3, R3\n"
+ "add R21, X\n"
+ "add R12, Y\n"
+ "mv X, R2"
;
System.out.println( "inputString = \"" + inputString + "\"" );
HashMap h = new HashMap();
h.put( "R1", "R2" );
h.put( "R2", "R3" );
h.put( "R3", "R1" );
h.put( "R12", "R21" );
h.put( "R21", "R12" );
Pattern p = Pattern.compile( "\\b(R(?:12?|21?|3))\\b");
Matcher m = p.matcher( inputString );
StringBuffer sbuff = new StringBuffer();
int lastEnd = 0;
while ( m.find()) {
int mstart = m.start();
if ( lastEnd < mstart ) {
sbuff.append( inputString.substring( lastEnd, mstart ));
}
String key = m.group( 1 );
String value = (String)h.get( key );
sbuff.append( value );
lastEnd = m.end();
}
if ( lastEnd < inputString.length() ) {
sbuff.append( inputString.substring( lastEnd ));
}
System.out.println( "sbuff = \"" + sbuff + "\"" );
Это может быть реализовано на Java с помощью следующих классов:
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
interface StringReplacer {
public CharSequence getReplacement( Matcher matcher );
}
class Replacementifier {
static Comparator keyComparator = new Comparator() {
public int compare( Object o1, Object o2 ) {
String s1 = (String)o1;
String s2 = (String)o2;
int diff = s1.length() - s2.length();
return diff != 0 ? diff : s1.compareTo( s2 );
}
};
Map replaceMap = null;
public Replacementifier( Map aMap ) {
if ( aMap != null ) {
setReplacements( aMap );
}
}
public setReplacements( Map aMap ) {
replaceMap = aMap;
}
private static String createKeyExpression( Map m ) {
Set set = new TreeSet( keyComparator );
set.addAll( m.keySet());
Iterator sit = set.iterator();
StringBuffer sb = new StringBuffer( "(" + sit.next());
while ( sit.hasNext()) {
sb.append( "|" ).append( sit.next());
}
sb.append( ")" );
return sb.toString();
}
public String replace( Pattern pattern, CharSequence input, StringReplacer replaceFilter ) {
StringBuffer output = new StringBuffer();
Matcher matcher = pattern.matcher( inputString );
int lastEnd = 0;
while ( matcher.find()) {
int mstart = matcher.start();
if ( lastEnd < mstart ) {
output.append( inputString.substring( lastEnd, mstart ));
}
CharSequence cs = replaceFilter.getReplacement( matcher );
if ( cs != null ) {
output.append( cs );
}
lastEnd = matcher.end();
}
if ( lastEnd < inputString.length() ) {
sbuff.append( inputString.substring( lastEnd ));
}
}
public String replace( Map rMap, CharSequence input ) {
// pre-condition
if ( rMap == null && replaceMap == null ) return input;
Map repMap = rMap != null ? rMap : replaceMap;
Pattern pattern
= Pattern.compile( createKeyExpression( repMap ))
;
StringReplacer replacer = new StringReplacer() {
public CharSequence getReplacement( Matcher matcher ) {
String key = matcher.group( 1 );
return (String)repMap.get( key );
}
};
return replace( pattern, input, replacer );
}
}
Есть ли причина не использовать дженерики? (Я мог бы просто добавить их сам, но, в конце концов, это ваш ответ.)
Нет причин. Я просто не знаю, потому что большая часть нашего кода поддерживается как до Java 5.
Вот менее подробный способ сделать это за один проход с использованием API нижнего уровня Matcher: appendReplacement() и appendTail().
import java.util.*;
import java.util.regex.*;
public class Test
{
public static void main(String[] args) throws Exception
{
String inputString
= "mv A, R3\n"
+ "mv R2, B\n"
+ "mv R1, R3\n"
+ "mv B, R4\n"
+ "add A, R1\n"
+ "add B, R1\n"
+ "add R1, R2\n"
+ "add R3, R3\n"
+ "add R21, X\n"
+ "add R12, Y\n"
+ "mv X, R2"
;
System.out.println(inputString);
System.out.println();
System.out.println(doReplace(inputString));
}
public static String doReplace(String str)
{
Map<String, String> map = new HashMap<String, String>()
{{
put("R1", "R2");
put("R2", "R3");
put("R3", "R1");
put("R12", "R21");
put("R21", "R12");
}};
Pattern p = Pattern.compile("\\bR\\d\\d?\\b");
Matcher m = p.matcher(str);
StringBuffer sb = new StringBuffer();
while (m.find())
{
String repl = map.get(m.group());
if (repl != null)
{
m.appendReplacement(sb, "");
sb.append(repl);
}
}
m.appendTail(sb);
return sb.toString();
}
}
Обратите внимание, что appendReplacement() обрабатывает строку замены для замены последовательностей $ n текстом из групп захвата, что нам не нужно в этом случае. Чтобы этого избежать, я передаю ему пустую строку, а затем использую вместо нее метод StringBuffer append().
Эллиотт Хьюз опубликовал готовую реализацию этого метода здесь. (Он имеет тенденцию подбрасывать ссылки на другие написанные им служебные классы, поэтому вы можете удалить тесты в его методе main() перед его компиляцией.)
Мое предложение заключалось бы в замене строк при чтении из самого файла Вы можете использовать RandomAccessFile. При чтении из файла посимвольно, Фактически вы можете проверить шаблон, а затем выполнить замену прямо там. И тогда вы можете записать в файл сразу весь контент. Думаю, это сэкономит вам больше времени.
Это хорошее начало для ответа на вопрос, но показ простого примера того, что вы описываете, улучшит его.
Почему вы сортируете ключи перед созданием регулярного выражения?