Как я могу заменить строки в тексте на Java? - PullRequest
5 голосов
/ 19 декабря 2008

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

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, поэтому решение должно быть на Яве тоже.

#!/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.

Ответы [ 5 ]

5 голосов
/ 19 декабря 2008

За последние две недели мне приходилось использовать этот алгоритм несколько раз. Так что здесь это второй самый многословный язык в мире ...

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-ified этими классами:

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 ); 
    }
}
2 голосов
/ 19 декабря 2008

Преимущество решения Perl заключается в замене всех строк за один раз, что-то вроде «транзакционно». Если у вас нет такой опции в Java (и я не могу придумать, как это сделать), вам нужно быть осторожным, заменив R1 => R2, затем R2 => R3. В этом случае оба R1 и R2 заменяются R3.

0 голосов
/ 12 ноября 2014

Мое предложение будет заменять строки при чтении из самого файла Вы можете использовать RandomAccessFile . Читая из файла символ за символом, Вы можете на самом деле проверить шаблон и затем выполнить замену там и тогда. И тогда вы можете записать все содержимое сразу в файл. Я думаю, это сэкономит вам больше времени.

0 голосов
/ 20 декабря 2008

Вот менее подробный способ сделать это за один проход, используя низкоуровневый 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 последовательностей текстом из групп захвата, что нам не нужно в этом случае. Чтобы избежать этого, я передаю пустую строку, а затем использую метод append() StringBuffer's.

Эллиот Хьюз опубликовал готовую реализацию этой техники здесь . (Он имеет тенденцию добавлять ссылки на другие написанные им служебные классы, поэтому вы можете удалить тесты в его методе main() перед его компиляцией.)

0 голосов
/ 19 декабря 2008

Вы можете использовать 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

Вы должны использовать некоторые переменные 'temp' для них:

R1 -> R@1
R2 -> R@3
R3 -> R1
R@(\d{1}) -> R\1

Вы могли бы написать библиотеку, которая бы сделала все это для вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...