2016-08-02 16 views
1

我试图用尽可能少的开销将相同的替换指令几千次应用于不同的输入字符串。我需要考虑两两件事是:替换字符串中的多个子字符串的有效且无干扰的方法

  1. 搜索字符串不一定都是相同的长度:一个可能只是“一”,另一种可能是“CH”,另一种可能是“SCH”
  2. 已经替换的内容不应再次替换:如果替换模式为[a-> e; e-> a],“beat”应该变成“baet”,而不是“baat”或“甜菜”。

考虑到这一点,这是我想出了代码:

public class Replacements { 
    private String[] search; 
    private String[] replace; 
    Replacements(String[] s, String[] r) 
    { 
     if (s.length!=r.length) throw new IllegalArgumentException(); 
     Map<String,String> map = new HashMap<String,String>(); 
     for (int i=0;i<s.length;i++) 
     { 
      map.put(s[i], r[i]); 
     } 
     List<String> sortedKeys = new ArrayList(map.keySet()); 
     Collections.sort(sortedKeys, new StringLengthComparator()); 
     this.search = sortedKeys.toArray(new String[0]); 
     Stack<String> r2 = new Stack<>(); 
     sortedKeys.stream().forEach((i) -> { 
      r2.push(map.get(i)); 
     }); 
     this.replace = r2.toArray(new String[0]); 
    } 
    public String replace(String input) 
    { 
     return replace(input,0); 
    } 
    private String replace(String input,int i) 
    { 
     String out = ""; 
     List<String> parts = Arrays.asList(input.split(this.search[i],-1)); 
     for (Iterator it = parts.iterator(); it.hasNext();) 
     { 
      String part = it.next().toString(); 
      if (part.length()>0 && i<this.search.length-1) out += replace(part,i+1); 
      if (it.hasNext()) out += this.replace[i]; 
     } 
     return out; 
    } 
} 

然后

String[] words; 
//fill variable words 
String[] s_input = "ou|u|c|ch|ce|ci".split("\\|",-1); 
String[] r_input = "u|a|k|c|se|si".split("\\|",-1); 
Replacements reps = new Replacements(s_input,r_input); 
for (String word : words) { 
    System.out.println(reps.replace(word)); 
} 

s_inputr_input将高达用户,因此他们只是举例,就像程序实际上不会使用println()

This代码确保首先查找更长的搜索字符串,并且还涵盖上面的第二个条件。

然而,它是相当昂贵的。什么是最有效的方式来完成我在这里做的事情(特别是如果words中的字符串数量非常大)?

随着我当前的代码,“沙发”应该被转换为“KUC”(除了没有,显然,它现在,多亏了-1 split(p,-1)

+0

你会遇到'split(“|”)'(参数是一个正则表达式)的麻烦。如果你真的必须使用'split(“\\ |”));但是最好是明确地构造你的地图,并将它作为参数传递给'Replacements'。 –

+0

'split(“|”)'部分只是为了说明's_input'和'r_input'内部的内容。实际的代码会以不同的方式派生出这些内容。但我会在这里编辑代码以消除这种情况。 – joelproko

+0

说实话,如果你想要尽可能少的开销,理想的解决方案是迭代char数组(一次)并跟踪历史记录,以替换任何代替多个char的任何东西。也就是抛弃任何正则表达式。 – Rogue

回答

1

这不是一个完整的解决方案但它显示了如何扫描输入并在一个遍中查找所有目标子字符串。您可以使用StringBuilder来汇总结果,并在当前的Map中查找替换项。使用开始索引和结束索引来处理复制不匹配的段。

public static void main(String[] args) throws Exception 
{ 
    Pattern p = Pattern.compile("(ou|ch|ce|ci|u|c)"); 
    Matcher m = p.matcher("auouuchcceaecxici"); 
    while (m.find()) 
    { 
     MatchResult r = m.toMatchResult(); 
     System.out.printf("s=%d e=%d '%s'\n", r.start(), r.end(), r.group()); 
    } 
} 

输出:

s=1 e=2 'u' 
s=2 e=4 'ou' 
s=4 e=5 'u' 
s=5 e=7 'ch' 
s=7 e=8 'c' 
s=8 e=10 'ce' 
s=12 e=13 'c' 
s=15 e=17 'ci' 

注意,在正则表达式中的字符串在下降长度正常工作的顺序进行排序。

0

人们可以从按键创建一个正则表达式模式,并将其留给该模块进行优化。

显然

"(ou|u|ch|ce|ci|c)" 

需要照顾CE/CI/C的,或者通过反向排序或立即作为树:

"(c(e|h|i)?|ou|u)" 

然后

String soughtKeys = "ou|u|ch|ce|ci|c"; // c last 
String replacements = "u|a|c|se|si|k"; 
Map<String, String> map = new HashMap<>(); 
... fill map 

Pattern pattern = Pattern.compile("(" + soughtKeys + ")"); 

for (String word : words) { 
    StringBuffer sb = new StringBuffer(); 
    Matcher m = pattern.matcher(word); 
    while (m.find()) { 
     m.appendReplacement(sb, map.get(m.group()); 
    } 
    m.appendTail(sb); 
    System.out.printf("%s -> %s%n", word, sb.toString()); 
} 

的优点是该正则表达式非常聪明(尽管很慢),并且替换不会替代文本。

0
public class Replacements 
{ 
    private String[] search; // sorted in descending length and order, eg: sch, ch, c 
    private String[] replace; // corresponding replacement 

    Replacements(String[] s, String[] r) 
    { 
     if (s.length != r.length) 
      throw new IllegalArgumentException(); 

     final TreeMap<String, String> map = new TreeMap<String, String>(Collections.reverseOrder()); 

     for (int i = 0; i < s.length; i++) 
      map.put(s[i], r[i]); 

     this.search = map.keySet().toArray(new String[map.size()]); 
     this.replace = map.values().toArray(new String[map.size()]); 
    } 

    public String replace(String input) 
    { 
     final StringBuilder result = new StringBuilder(); 

     // start of yet-to-be-copied substring 
     int s = 0; 

     SEARCH: 
     for (int i = s; i < input.length(); i++) 
     { 
      for (int p = 0; p < this.search.length; p++) 
      { 
       if (input.regionMatches(i, this.search[p], 0, this.search[p].length())) 
       { 
        // append buffer and replacement 
        result.append(input, s, i).append(this.replace[p]); 

        // skip beyond current match and reset buffer 
        i += this.search[p].length(); 
        s = i--; 

        continue SEARCH; 
       } 
      } 
     } 

     if (s == 0) // no matches? no changes! 
      return input; 

     // append remaining buffer 
     return result.append(input, s, input.length()).toString(); 
    } 
} 
+0

不幸的是,如果您输入'[ou,u,c,ch,ce,ci],'this.search'和'this.replace'最终分别为'[ou,u]'和'[si,k] '和'[u,a,k,c,se,si]'作为's'和'r'放入您的版本'Replacement(String [] s,String [] r)' – joelproko

+0

@joelproko ...可能由于'''StringLengthComparator''中断了,它在TreeMap中设置了等长的字符串。在TreeMap中只需使用'''Collections.reverseOrder()'''(不带参数)就可以实现反向自然排序。一个简单的反向自然顺序的搜索关键字完全可以处理''''[c,ch,ce,ci]'''的情况,因为更长的单词自然会在其较短的前缀之前反向排序。没有必要明确检查搜索关键字的长度。 – Robin479

+0

尽管您的替换函数显着更好。在我用示例中的搜索/替换对来检查英式英语hunspell字典中的所有小写字母条目(63230个单词)的基准测试中,它的运行时间约为每次运行约23毫秒(通过整个单词表) ,平均超过10000次运行。在我的例子中,拼凑在一起的函数在完成相同的任务时每次运行需要大约140毫秒(平均只有100次运行,没有打算更高)。 (这两个基准没有输出或将output()函数的输出存储到任何东西) – joelproko

相关问题