Codingbat 挑战赛:sumNumbers Stream API 解决方案

Codingbat challenge: sumNumbers Stream API Solution

提问人:Evgeniy 提问时间:6/4/2022 最后编辑:VLAZEvgeniy 更新时间:8/27/2023 访问量:209

问:

给定 CodingBat 的任务 sumNumbers

给定一个字符串,返回字符串中出现的数字的总和, 忽略所有其他字符。数字是一系列或多个数字 字符。1

(注意:测试 char 是否为 1 的字符 , , ..., . 将字符串转换为 .)Character.isDigit(char)a019Integer.parseInt(string)aint

sumNumbers("abc123xyz")  →   123
sumNumbers("aa11b33")    →   44
sumNumbers("7 11")       →   18

我对这个问题的解决方案如下:

public int sumNumbers(String str) {
  int sum = 0;
  
  java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("[0-9]+").matcher(str);
    while (matcher.find()) {
        sum += Integer.parseInt(matcher.group());
    }
    
  return sum;
}

是否可以使用 Stream API 解决这个问题?

正则表达式 字符串 java-stream

评论

0赞 Alexander Ivanchenko 6/10/2022
以防万一,1. 原始流的性能优于对象流,2.使用收集器会产生额外的成本。也就是说,对 的操作将比 更快。sum()IntStreamCollectors.summingInt()

答:

1赞 Alexander Ivanchenko 6/4/2022 #1

是否可以使用 Stream API 解决这个问题?

使用不由数字组成的子字符串拆分给定的字符串。正则表达式匹配由一个或多个非数字字符组成的字符串。结果将是一个数字字符串数组。.split("\\D+")"\\D+"

在数组上创建一个流,并筛选出不为空的字符串。然后将字符串解析为 with 并作为终端操作应用。intmapToInt()sum()

该解决方案通过了 CodingBat 上的所有测试:

public int sumNumbers(String str) {
    return Arrays.stream(str.split("\\D+"))
        .filter(s -> !s.isEmpty())
        .mapToInt(Integer::parseInt)
        .sum();
}

由于在数组的开头只能有一个空字符串,因此为了减少在流流管道中执行的操作的数量,可以用 替换为 。.split("\\D+")filter()dropWhile()

如果第一个字符串空,它将跳过该字符串,并且在遇到第一个非空元素后,将不会应用此检查。即传递给 的谓词在大多数时候都会被执行。如果字符串很长(否则所有优化都无关紧要),它比使用 .dropWhile()dropWhile()2replaceAll()

public static int sumNumbers(String str) {
    return Arrays.stream(str.split("\\D+"))
        .dropWhile(String::isEmpty)
        .mapToInt(Integer::parseInt)
        .sum();
}

注意:Java 9 及更高版本可用。CodingBat 仍然在 Java 8 上,因此它不知道这个特性。尽管如此,它仍然是一个有效且性能良好的解决方案。 您可以在 IDE 中对其进行测试,并使用在线演示dropWhile()


测试(第一种解决方案):

enter image description here

1赞 WJS 6/4/2022 #2

是的。

String[] data = { "abc123xyz", "aa11b33", "7 11" };
  • \\D- 拆分除非数字字符串以外的任何数字
  • 从拆分中筛选空字符串
  • 转换为 an 并将值求和。int
  • 返回一个条目以显示字符串和总和(不需要,但显示关联)
Arrays.stream(data).map(
        str -> new AbstractMap.SimpleEntry<String, Integer>(
                str,
                Arrays.stream(str.split("\\D+"))
                        .filter(s -> !s.isBlank())
                        .mapToInt(Integer::parseInt).sum()))
        .forEach(e -> System.out.printf("%-10s -> %d%n",
                e.getKey(), e.getValue()));

指纹

abc123xyz  -> 123
aa11b33    -> 44
7 11       -> 18

如果您只想要总和,您可以执行以下操作:

public static int getSum(String str) {
    return Arrays.stream(str.split("\\D+"))
                .filter(s -> !s.isBlank())
                .mapToInt(Integer::parseInt)
                .sum();
}

评论

0赞 Bohemian 6/5/2022
空白只能在开始时出现一次,因此使用意味着您可以删除筛选器。str.replaceAll("^\\D+", "").split("\\D+")
0赞 Alexander Ivanchenko 6/6/2022
@Bohemian / 必须生成字符串的副本,因此匹配的子字符串位于最开头并不重要。它仍然需要遍历整个字符串。无需支付额外的迭代成本,也无需在内存中分配一个新字符串,以消除对 .replaceAllreplaceFirstfilter()
2赞 dani-vta 6/4/2022 #3

若要使流实现更接近原始解决方案,您仍然可以使用 和 ,然后流式传输 的结果。PatternMatcherMatcher

public int sumNumbers(String s) {
    return Pattern.compile("\\d+").matcher(s).results()
        .collect(Collectors.summingInt(m -> Integer.valueOf(m.group())));
}

输出

123
44
18

下面是一个链接,用于测试具有预期输出的代码:

https://www.jdoodle.com/iembed/v0/rRS

2赞 K.Nicholas 6/4/2022 #4

你们这些疯子。对我来说,您可以将任何函数转换为流。Map/Reduce是一种常见的模式:

int s = Stream.of("abc123xyz").mapToInt(str->{
    int sum = 0;

    java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("[0-9]+").matcher(str);
    while (matcher.find()) {
        sum += Integer.parseInt(matcher.group());
    }

    return sum;
}).sum();
System.out.println(s);