Как разобрать строку, используя спецификацию строки? - PullRequest
1 голос
/ 07 мая 2019

Я хочу написать метод для разбора строк, содержащих имена людей и их возраст. Например:

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]");

, где Data и Parser определены следующим образом:

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? Другими словами, как можно проанализировать строку, используя спецификацию строки?

Ответы [ 4 ]

1 голос
/ 29 мая 2019

Здесь мы можем получить выражение и собрать желаемые результаты в две группы, например:

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

, где наши желаемые имена находятся в этой группе ([a-z-]+), а информация о возрасте находится в этой(\d+), а остальное можно просто записать в сценарий.

TEST

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

Схема RegEx

jex.im визуализирует регулярные выражения:

enter image description here

DEMO 2

Советы

На основе Здим совет:

(1) Я думаю, что (?: \ s +)?(хотя бы один пробел, но все это необязательно) такой же, как \ s * (необязательные пробелы)

(2) Во второй группе пробелов я думаю, что вы хотите, чтобы хотя бы пробелтак что просто \ s +.

Мы можем значительно упростить и изменить наше начальное выражение до чего-то похожего на:

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

DEMO

0 голосов
/ 07 мая 2019

Создайте регулярное выражение из 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]
0 голосов
/ 07 мая 2019

Это работает для данных и должно работать для других вариантов.Но другие специальные символы не могут быть экранированы должным образом.

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));
   }
}
  • Экраны () и [] экранированы, чтобы ваши примеры работали, поскольку они являются специальными символами для регулярных выражений.

  • Одна из захваченных строк должна быть проверена на все цифры, чтобы обеспечить правильное преобразование имени и возраста.

  • Это довольно грубая силаподход, и он не масштабируется хорошо.

0 голосов
/ 07 мая 2019

Я прочитал бы спецификацию и использовал бы ее для анализа всех частей строки, которые не находятся между% N и% A, поскольку они просто усложняют вашу логику.Затем вы должны выяснить, какие ограничения вы намереваетесь наложить на этот API, как если бы вы не накладывали на него ограничений, поскольку он недетерминирован.Например,

H'mil99 - это шаблон% N% A, но это имя N'mil с возрастом 99 или H'mil9 с возрастом 9.

Если вы выбрали логические ограничения без чисел вимя и отсутствие альфа в возрасте, а затем определить возраст, выполнив matcher.group в [0-9] *, вставив его в подстроку вместе с частью между% A и% N, а остальное -% N

...