Closed eulerlcs closed 7 years ago
代码我已经写好了,我先提供一下思路,有兴趣的同学可以想想,写写代码,过几天我公布我的答案。 真正的代码不超过30行。
先写出占位符的正则表达式,然后用Matcher.find()查找模板字符串,Matcher.group()就是占位符。
把占位符替换成正则表达式的子表达式(.*?)
,注意在替换后的正则表达式字符串前后要加 ^
和 $
,理由大家想想。
同样,用Matcher.find()查找文本字符串,Matcher.group()就是替换后的真实值。
如果模板字符串存在特殊字符,比如模板字符串中有且仅有一个左括号时,如果不进行特殊处理,在第三步中改造后的正则表达式有语法错误,是不正确的。
/**
* Matcher.group的例子:匹配 字母-数字
*
* <pre>
* group(0):正则表达式的匹配值
* </pre>
*/
@Test
public void test05_00() {
Pattern p = Pattern.compile("([a-z]+)-(\\d+)");
Matcher m = p.matcher("type x-235, type y-3, type zw-465");
while (m.find()) {
System.out.println(m.group());
}
}
我来试一试,templateReplace()函数代码不超过30行^_^,欢迎大佬批评指正,想自己写的小伙伴可以先不看,以免影响思路。
`
public void test(){
String tempStr = "【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link}";
String textStr ="【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有";
Map<String, String> map = templateReplace(tempStr, textStr);
// 遍历map
for (Map.Entry<String, String> entry : map.entrySet()){
System.out.println(entry.getKey() + " -- " + entry.getValue());
}
}
public Map<String, String> templateReplace(String tempStr, String textStr){
Map<String, String> map = new HashMap<>();
List<String> list = new ArrayList<>();
Pattern p = Pattern.compile("(\\$\\{.+?\\})");
Matcher m = p.matcher(tempStr);
// 找到占位符,并将其替换为(.*?)
while(m.find()){
list.add(m.group());
map.put(m.group(), "");
tempStr = tempStr.replace(m.group(), "(.*?)");
}
// 此处如果不加开始符^和结束符$的话,由于"?"是最短匹配,最后一个文本会是空字符串,即匹配不到示例中的 "没有"
tempStr = "^" + tempStr + "$";
p = Pattern.compile(tempStr);
m = p.matcher(textStr);
while(m.find()){
for (int i = 1; i <= m.groupCount(); i++){
map.put(list.get(i - 1), m.group(i));
}
}
return map;
}
`
@BradyWZH 对于例子的处理,你的结果是对的,有三个小地方,再改一下就完美了。 在和深圳-滴滴滴(69671710) 的讨论中,如果字符串中存在某些特殊字符,要特殊处理。 我 思路 里又加了第四点。
下边这行,占位符的正则表达式外层的括号是多余的。
Pattern p = Pattern.compile("(\\$\\{.+?\\})");
改造模板字符串使其成为正则表达式的处理,可以replaceAll一次性作成,当然你那样做没有错。可以写成下面这样。
// 模板字符串中变量的正则表达式
String keyRegex = "\\$\\{.*?\\}";
// **关键想法** 把模板字符串改装成正则表达式
String tmplRegex = "^" +tempStr.replaceAll(keyRegex, "(.*?)") + "$";
第二个 while(m.find())
用 if(m.find())
就可以,因为你做的是整句匹配,不存在多次匹配。
@BradyWZH 升一下级,在原来的模板字符串前加一个左括号,你再运行一下你的程序,看看还能得到想要的结果吗?
模板字符串:(【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link} 文本字符串:(【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有
@eulerlcs 谢谢大佬指出问题,加上左括号的情况下程序会报错,原因应该是当把模板字符串替换成正则表达式之后,原来文本中的特殊符号需要加转义字符,否则就会出现错误,我去想想怎么改会更好些^_^
@eulerlcs 换了一种思路,虽然解决了问题,但是感觉似乎过于复杂了,还请各位大佬给指点指点。
`
public void test() {
String tempStr = "(【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link}";
String textStr = "(【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有";
Map<String, String> map = templateReplace1(tempStr, textStr);
// 遍历map
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -- " + entry.getValue());
}
}
public Map<String, String> templateReplace1(String tempStr, String textStr) {
Map<String, String> map = new HashMap<>();
// 找出匹配的占位符
List<String> tempList = findAll("\\$\\{.+?\\}", tempStr);
// 相当于是一个唯一的符号,也可用其他替代
Long currentTime = System.currentTimeMillis();
// 必须保证字符串中不能存在
while(tempStr.contains((++currentTime).toString()));
tempStr = tempStr.replaceAll("\\$\\{.+?\\}", currentTime.toString());
// 【工银信用卡】于、至、申办奋斗卡,无年费,赢郎平签名排球!详情 等替换成唯一字符
for (String str : tempStr.split(currentTime.toString())){
if ("".equals(str)) continue ;
textStr = textStr.replace(str, currentTime.toString());
}
String textArr[] = textStr.split(currentTime.toString());
int j = 0;
// 由于String.split()方法的原因,得到数组的第一个可能是空字符串,即:""
if ("".equals(textArr[j])) j++;
for (int i = 0; i < tempList.size(); i++, j++)
map.put(tempList.get(i), textArr[j]);
return map;
}
public List<String> findAll(String regex, String replacement){
List<String> list = new ArrayList<>();
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(replacement);
// 找到占位符,并将其添加到list中
while (m.find()) {
list.add(m.group());
}
return list;
}
`
@BradyWZH 你的想法很好。有一个小错误,有一地方应该用replaceFirst,你用了replace,导致下边这个情况求不出解
tempStr = "a${startTime}b${endTime}";
textStr = "abbc";
我把你的代码改了一下,你参考以下
@Test
public void test_BradyWZH() {
String tempStr = null;
String textStr = null;
tempStr = "【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link}";
textStr = "【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有";
tempStr = "a${startTime}b${endTime}";
textStr = "abbc";
tempStr = "(${startTime}至${endTime}";
textStr = "(昨天至今天";
tempStr = "a${startTime}b${endTime}";
textStr = "abbc";
// 模板字符串中占位符号(变量)的正则表达式
String keyRegex = "\\$\\{.+?\\}";
// 找出所有的占位符
List<String> keyList = new ArrayList<>();
{
Pattern p = Pattern.compile(keyRegex);
Matcher m = p.matcher(tempStr);
while (m.find()) {
keyList.add(m.group());
}
}
// 找出一个字符串:模板字符串中不包含它,文本字符串中也不包含它
String onlyStr = null;
{
// 相当于是一个唯一的符号,也可用其他替代
Long currentTime = System.currentTimeMillis();
// 必须保证字符串中不能存在
while (tempStr.contains(currentTime.toString())) {
currentTime++;
}
onlyStr = currentTime.toString();
}
// 找出模板字符串中,用占位符分割的字符串列表,也就是固定字符串的列表
List<String> literalList = new ArrayList<>();
{
String temp = tempStr.replaceAll(keyRegex, onlyStr);
String[] tempArray = temp.split(onlyStr);
for (int i = 0; i < tempArray.length; i++) {
if (i == 1 || i == tempArray.length - 1) {
if ("".equals(tempArray[i])) {
continue;
}
}
literalList.add(tempArray[i]);
}
}
// 找出文本字符串中替换值列表
List<String> valueList = new ArrayList<>();
{
String text = textStr;
for (String literal : literalList) {
text = text.replaceFirst(literal, onlyStr);
}
String valueArray[] = text.split(onlyStr);
for (int i = 0; i < valueArray.length; i++) {
if (i == 1 || i == valueArray.length - 1) {
if ("".equals(valueArray[i])) {
continue;
}
}
valueList.add(valueArray[i]);
}
}
// 输出结果
if (valueList.isEmpty()) {
System.out.println("the text file format is not correct");
} else {
for (int i = 0; i < keyList.size(); i++) {
System.out.println(keyList.get(i) + "\t\t" + valueList.get(i));
}
}
}
@eulerlcs 明白了很重要的一点,就是原来普通字符加不加\,它的意思并不会改变,比如说:\:,\~,\中 等等,匹配的还是 :、~、中这些字符本身,之前以为加了就变了,所以就觉得第一种想法判断起来太麻烦[笑哭];第二份代码确实有考虑不周的地方,忽略了一些特殊情况,非常感谢大佬的指点。
我是长沙-刘春生(41689722)。 深圳-滴滴滴(69671710) 在群里提了一个问题,我想想了,这个问题正是体现正则表达式威力的好例子,所以在这纪录一下,供大家讨论,我把他的问题整理了一下。
问题
有一个模板字符串,占位符(或者也可以叫变量)的格式是
${变量名}
,同时有一个把各个占位符替换成真实值之后的文本字符串,请列出占位符和其替换后真实值的键值对。例子
问题 模板字符串:【工银信用卡】于${startTime}至${endTime}申办奋斗卡,无年费,赢郎平签名排球!详情${link} 文本字符串:【工银信用卡】于昨天至今天申办奋斗卡,无年费,赢郎平签名排球!详情没有
答案