Как заменить строки в тексте на Java?

В проекте у нас есть текстовые файлы, которые выглядят так:

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.

Почему вы сортируете ключи перед созданием регулярного выражения?

user3458 19.12.2008 17:31

поэтому R12 будет сопоставлен до R1 (иначе R12 никогда не будет сопоставлен), комментарий теперь также добавлен в код.

szabgab 20.12.2008 00:11

Сортировка их на самом деле не является решением; если текст содержит «R13», он будет заменен на «R23». Вы хотите, чтобы регулярное выражение соответствовало тому, что вы ему указываете, и ничего больше. Границы слов будут делать это в этом случае: / \ b (R1 | R2 | R3 | R12 | R21) \ b /

Alan Moore 20.12.2008 06:39

вы правы, добавление \ b вокруг регулярного выражения гарантирует, что R122, который следует оставить в покое, не соответствует R12 и изменен на R212

szabgab 20.12.2008 12:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
4
2 340
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы можете использовать 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 () обрабатывает регулярные выражения, цитирование его аргументов, вероятно, является хорошей идеей.

Darron 19.12.2008 17:26

Неужели это решение не сработает? Ваша первая итерация заменит «R1» на «R2» повсюду, затем ваша вторая итерация заменит «R2» на «R3» повсюду, включая значения, которые изначально были "R1" - в результате R1, R2 и R3 будут * все будет отображаться как R3 к концу.

Adrian 19.12.2008 17:52

И поскольку на карте нет гарантии заказа, этот может или не может случиться, что делает его еще более интересным ...

Adrian 19.12.2008 17:53

@ Адриан: Я с вами - я сомневаюсь, что это работа с циклическими заменами в вопросе.

Jonathan Leffler 19.12.2008 18:10

Вы можете получить упорядоченную карту, переключившись на TreeMap.

Powerlord 19.12.2008 19:01

Другая проблема: строки Java неизменяемы, поэтому str.replaceAll () создает новую строку, но вы ее отбрасываете.

Alan Moore 21.12.2008 01:24

Преимущество решения 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 ); 
    }
}

Есть ли причина не использовать дженерики? (Я мог бы просто добавить их сам, но, в конце концов, это ваш ответ.)

Michael Myers 19.12.2008 20:30

Нет причин. Я просто не знаю, потому что большая часть нашего кода поддерживается как до Java 5.

Axeman 20.12.2008 01:45

Вот менее подробный способ сделать это за один проход с использованием 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. При чтении из файла посимвольно, Фактически вы можете проверить шаблон, а затем выполнить замену прямо там. И тогда вы можете записать в файл сразу весь контент. Думаю, это сэкономит вам больше времени.

Это хорошее начало для ответа на вопрос, но показ простого примера того, что вы описываете, улучшит его.

brandonscript 12.11.2014 21:11

Другие вопросы по теме