【问题标题】:How to parse a string using a string specification?如何使用字符串规范解析字符串?
【发布时间】:2019-09-24 15:29:50
【问题描述】:

我想写一个方法来解析包含人名和年龄的字符串。例如:

Manuel 8
Mustafa 16
Zhihao 12
Itsuki 12
Louis 11
Farah 11

即字符串的规格为%N %A,其中%N代表姓名,%A代表年龄。

但是,字符串的规范不是固定的(例如,它可能是另一个文档中的%N age:%A%N (%A)),因此解析方法应该能够将规范作为其参数之一。

换句话说,解析方法应该是这样工作的:

Data d1 = Parser.parse("Indira 15", "%N %A");
Data d2 = Parser.parse("12 Shu-chen", "%A %N");
Data d3 = Parser.parse("Hana (12)", "%N (%A)");
Data d4 = Parser.parse("Name: Sophia [12]", "Name: %N [%A]");

DataParser 的定义如下:

public class Data {
    private String name;
    private int age;

    public Data(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // + getter and setter methods.
}

public class Parser {
    public static Data parse(String s, String specification) {
        // --- What to do here? ---
        return (new Data(name, age));
    }
}

Parser.parse怎么写?换句话说,如何使用字符串规范来解析字符串?

【问题讨论】:

  • 关键字:Regular Expression
  • @RickyMo 是的,我知道如何使用正则表达式解析具有固定规范的字符串,但我不确定如何解析具有可变规范的字符串。
  • 您可以使用捕获组来捕获变量。对%N 使用(.+),对%A 使用([0-9]+),并在Java 中使用Matcher.group() 检索捕获的组。正则表达式示例:https://regexr.com/4dgev。但是,您可能需要进行额外的分析,以确定是年龄优先还是姓名优先。
  • 这就是你的意思:“规范”字面意思说明了格式,只有%N%A 表示姓名/年龄?然后做到这一点:从specification 中形成正则表达式,在其中将%N 替换为([\w-']+)(添加名称中可能包含的任何其他内容,或使用[^0-9]+)和%A([0-9]+) .然后,当您将s 与此匹配时,您将获得姓名和年龄。要查看哪个是哪个,请再次参考您的规范(%N%A 是第一个吗?)。

标签: java regex parsing regex-group regex-greedy


【解决方案1】:

在这里,我们可以有一个表达式,并在两组中收集我们想要的输出,例如:

((?:\s+)?([a-z-]+)(?:\s+)?)|(\d+)

我们想要的名字在这个([a-z-]+) 组中,年龄信息在这个(\d+) 中,其余的可以简单地编写脚本。

测试

import java.util.regex.Matcher;
import java.util.regex.Pattern;

final String regex = "((?:\\s+)?([a-z-]+)(?:\\s+)?)|(\\d+)";
final String string = "Indira 15\n"
     + "12 Shu-chen\n"
     + "Hana (12)\n"
     + "Sophia [12]\n"
     + "  Manuel 8\n"
     + "Mustafa  16\n"
     + "Zhihao    12\n"
     + "Itsuki 12\n"
     + "Louis 11\n"
     + "Farah 11";

final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
final Matcher matcher = pattern.matcher(string);

while (matcher.find()) {
    System.out.println("Full match: " + matcher.group(0));
    for (int i = 1; i <= matcher.groupCount(); i++) {
        System.out.println("Group " + i + ": " + matcher.group(i));
    }
}

DEMO

正则表达式电路

jex.im 可视化正则表达式:

DEMO 2

建议

根据zdim的建议:

(1) 我认为 (?:\s+)? (至少一个空格,但整件事是 可选)与 \s* (可选空格)相同

(2) 在第二组空间中,我认为您需要 至少一个空格,所以只需 \s+。

我们可以大大简化和修改我们的初始表达式,类似于:

(\s*([a-z-]+)\s+)|(\d+)

DEMO

【讨论】:

  • 感谢您的建议。您确定此方法适用于* Name: John; Age: 15 之类的字符串(字符串规范:* Name: %N; Age: %A
  • @Flux 好吧,不,它不会(那是一个真正的星号吗??)。您要问的内容非常广泛,对于通用解析器有效。 (请注意,您没有给出要支持的模式的明确列表。)这将需要比适合 SO 帖子的代码多得多的代码。这个答案将涵盖其中一种情况(可能稍作调整)
【解决方案2】:

我会阅读规范并使用它来解析字符串中不在 %N 和 %A 之间的所有部分,因为它们只会使您的逻辑复杂化。然后,您必须弄清楚您打算对该 api 施加什么约束,就好像您没有对其非确定性施加约束一样。例如

H'mil99 是模式 %N%A,但是是年龄为 99 的 nam H'mil 或年龄为 9 的 H'mil9。

如果您选择名称中没有数字且年龄中没有字母的逻辑限制,则通过在 [0-9]* 上执行 matcher.group 来识别年龄,将其与 %A 和%N,其余为 %N

【讨论】:

    【解决方案3】:

    specification 构建一个正则表达式,例如如下所示。

    注意使用Pattern.quote() 以确保specification 中的特殊字符不会被解释为正则表达式。

    public static Data parse(String s, String specification) {
        // Determine order of value markers
        int nameIdx = specification.indexOf("%N");
        if (nameIdx == -1)
            throw new IllegalArgumentException("Specification is missing %N: " + specification);
        int ageIdx = specification.indexOf("%A");
        if (ageIdx == -1)
            throw new IllegalArgumentException("Specification is missing %A: " + specification);
    
        // Build regex
        String regex;
        if (nameIdx < ageIdx) {
            regex = Pattern.quote(specification.substring(0, nameIdx)) + "(\\S+)" +
                    Pattern.quote(specification.substring(nameIdx + 2, ageIdx)) + "(\\d+)" +
                    Pattern.quote(specification.substring(ageIdx + 2));
        } else {
            regex = Pattern.quote(specification.substring(0, ageIdx)) + "(\\d+)" +
                    Pattern.quote(specification.substring(ageIdx + 2, nameIdx)) + "(\\S+)" +
                    Pattern.quote(specification.substring(nameIdx + 2));
        }
    
        // Parse string
        Matcher m = Pattern.compile(regex).matcher(s);
        if (! m.matches())
            throw new IllegalArgumentException("String does not fit specification '" + specification + "': " + s);
        String name, age;
        if (nameIdx < ageIdx) {
            name = m.group(1);
            age = m.group(2);
        } else {
            name = m.group(2);
            age = m.group(1);
        }
        return new Data(name, Integer.parseInt(age));
    }
    

    测试

    System.out.println(parse("Indira 15", "%N %A"));
    System.out.println(parse("12 Shu-chen", "%A %N"));
    System.out.println(parse("Hana (12)", "%N (%A)"));
    System.out.println(parse("Name: Sophia [12]", "Name: %N [%A]"));
    

    输出(假设 toString()Data 类中实现)

    Data[name=Indira, age=15]
    Data[name=Shu-chen, age=12]
    Data[name=Hana, age=12]
    Data[name=Sophia, age=12]
    

    【讨论】:

      【解决方案4】:

      这适用于给定的数据,并且应该适用于其他变体。但其他特殊字符可能无法正确转义。

      import java.util.*;
      import java.util.regex.*;
      
      public class Parser2 {
      
         public static void main(String[] args) {
            Data d1 = Parser.parse("Indira 15", "%N %A");
            Data d2 = Parser.parse("12 Shu-chen", "%A %N");
            Data d3 = Parser.parse("Hana (12)", "%N (%A)");
            Data d4 = Parser.parse("Name: Sophia [12]", "Name: %N [%A]");
      
            System.out.println(d1);
            System.out.println(d2);
            System.out.println(d3);
            System.out.println(d4);
      
         }
      
      }
      
      class Data {
         private String name;
         private int    age;
      
         public Data(String name, int age) {
            this.name = name;
            this.age = age;
         }
      
         public String toString() {
            return "name = " + name + ", " + "age = " + age;
         }
      }
      
      class Parser {
         private static Map<String, String> spec =
               Map.of("%A", "(\\d+)", "%N", "([A-Za-z-]+)");
      
         public static Data parse(String s, String specification) {
            specification = specification.replaceAll("\\(", "\\\\(");
            specification = specification.replaceAll("\\)", "\\\\)");
            specification = specification.replaceAll("\\]", "\\\\]");
            specification = specification.replaceAll("\\[", "\\\\[");
      
            for (String r : spec.keySet()) {
               specification = specification.replace(r, spec.get(r));
            }
      
            Matcher m = Pattern.compile(specification).matcher(s);
            String m1 = "", m2 = "";
            if (m.find()) {
               m1 = m.group(1);
               m2 = m.group(2);
            } else {
               return null;
            }
            String name;
            int age;
            if (m1.matches("\\d+")) {
               age = Integer.parseInt(m1);
               name = m2;
            }
            else {
               age = Integer.parseInt(m2);
               name = m1;
            }
      
            return (new Data(name, age));
         }
      }
      
      • () 和 [] 被转义以允许您的示例按原样工作 是正则表达式的特殊字符。

      • 需要对捕获的字符串之一进行所有数字测试 允许正确转换姓名和年龄。

      • 这是一种相当蛮力的方法,它不能很好地扩展。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-10
        • 2015-01-13
        • 2019-04-19
        • 1970-01-01
        • 2020-09-04
        • 1970-01-01
        相关资源
        最近更新 更多