如何使用java中的regex从分隔字符串中仅提取特定类型值

时间:2017-05-24 10:28:37

标签: regex java-8

我有一个如下字符串:

SOMETEXT(ABC, DEF, 5, 78.0, MNO)

我想用正则表达式解析它以获得ABC,DEF和MNO的List<String>。即。我想避免任何类型的数字,只提取文本。

总的来说,我的结构如下:

class Detail {
    String name;
    String type;
}

// Sample values of name = "test1" type = "SOMETEXT(ABC,5)"
// Sample values of name = "test2" type = "SOMETEXT(ABC,DEF,2.2)"
// Sample values of name = "test3" type = "SOMETEXT(ABC,DEF)"

List<Detail>我想得到Map<String, List<String>>其中list<String>提取类型和密钥的文本为name,如果可能,使用流的java 8方式。

直到现在我必须从字符串中获得第一个文本,我按照以下方式执行:

Map<String, List<String>> assignOperatorMap = details
    .stream()
    .collect(groupingBy(md -> md.getName(), mapping((Details m) ->
        m.getType().substring(m.getType().indexOf("(") + 1,
        m.getType().indexOf(")")).split("\\,")[0] , 
        Collectors.toList()
    )));

以上代码给了我: {test1=[ABC], test2=[ABC], test3=[ABC]}只是第一个值。

2 个答案:

答案 0 :(得分:0)

如果订单无关紧要,你可以尝试这样的事情:

final List<Detail> details = Arrays.asList(
    new Detail("test1", "SOMETEXT(ABC, DFD)"),
    new Detail("test2", "SOMETEXT(ABC,DEF,2.2)"),
    new Detail("test3", "SOMETEXT(ABC,DEF,GHF)")
);

final Map<String, List<String>> map = details
    .stream()
    .collect(Collectors.groupingBy(
        Detail::getName,
        Collectors.mapping(
            detail -> {
                final String[] values = detail.getType().split("[,(). 0-9]+");
                return Arrays.copyOfRange(values, 1, values.length);
            },
            Collector.of(ArrayList::new,
                (list, array) -> list.addAll(Arrays.asList(array)),
                    (source, target) -> {
                        source.addAll(target);
                        return source;
                    }
                )
            )
        ));

System.out.println(map);
// Output: {test2=[ABC, DEF], test3=[ABC, DEF, GHF], test1=[ABC, DFD]}

答案 1 :(得分:0)

这个怎么样:

List<Detail> details = new ArrayList<>();
details.add(new Detail("test1", "SOMETEXT(ABC,5)"));
details.add(new Detail("test2", "SOMETEXT(ABC,DEF,2.2)"));
details.add(new Detail("test3", "SOMETEXT(ABC,DEF)"));

Map<String, List<String>> assignOperatorMap = details.stream()
    .flatMap(d -> Arrays.stream(d.getType()
            .replaceAll("\\w+\\((.*)\\)", "$1")
            .split(","))
            .filter(s -> s.matches("[A-Za-z_]+"))
            .map(s -> new SimpleEntry<>(d.getName(), s)))
    .collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())));

System.out.println(assignOperatorMap); // {test2=[ABC, DEF], test3=[ABC, DEF], test1=[ABC]}

我们的想法是首先在括号中捕获字符串:.replaceAll("\\w+\\((.*)\\)", "$1"),然后将其拆分为,并过滤掉与[A-Za-z_]+不匹配的内容。

还有一个创建一堆Entry<String, String>(名称,类型)的技巧,以避免必须流两次,因为每个Detail现在可以产生多个类型字符串,我们必须以某种方式< em>将它们展平成List<String>(而不是List<String[]>)。 (最好用Java 9的flatMapping收集器来完成,但它还没有。)

  

如何扩展此正则表达式以忽略某些文本,例如HOURS,MINUTES

您可以使用要忽略的字词创建Set<String>,并在第二次filter调用中根据该字词进行过滤:

Set<String> ignore = new HashSet<>();
ignore.add("HOURS");
ignore.add("MINUTES");

...
.filter(s -> s.matches("[A-Za-z_]+"))
.filter(s -> !ignore.contains(s)) // <-- extra filter call
.map(s -> new SimpleEntry<>(d.getName(), s)))
...