Dart:检查字符串中特定符号是否为数字的最快方法是什么?

8

当然我可以通过将其与10个数字进行比较甚至使用正则表达式来完成,但我正在寻找最快的方式。

以下是我目前想到的方法,看起来合理吗?

int _zero = "0".codeUnits[0];
int _nine = "9".codeUnits[0];
bool isDigit(String s, int idx) => 
 s.codeUnits[idx] >= _zero && s.codeUnits[idx] <= _nine; 

我有些惊讶,标准库中居然没有找到这种方法,希望只是我错过了它。


1
如果你想要快速,不要使用.codeUnits。它会创建一个包装字符串的对象,然后[idx]只是执行string.codeUnitAt(idx)。直接这样做即可。编译器可能会优化掉开销,但为什么要冒险呢?你也不想对同一索引调用两次codeUnitAt - 使用变量。常量_zero和_nine应该是真正的常量,而不是懒惰初始化的可变全局变量,所以const _zero = 0x30; const _nine = 0x39;这样编译器就知道它们是常量了。我会写成{ final char = s.codeUnitAt(idx); return _nine >= char && zero <= char; } - lrn
3个回答

13
尝试:

Try:

bool isDigit(String s, int idx) => (s.codeUnitAt(idx) ^ 0x30) <= 9;

为了速度。


我们有一个赢家 - 在我的微基准测试中,它比之前最快的代码快三倍。位运算技巧也很优雅。谢谢! - Andrew Skalkin
2
我知道这是一个老问题,但我想指出这种方法现在可以在Quiver包中使用。https://pub.dev/documentation/quiver/latest/quiver.strings/isDigit.html - Mateus Felipe

7

我为各种替代方案进行了快速的微基准测试,似乎Günter's method和暴力检查之间几乎是平局。我更喜欢Günter的方法,因为它更加优美,但如果性能绝对关键,似乎暴力方法可能会略胜一筹。基准测试对于将返回true的索引和将返回false的索引分别运行每种方法一次。

暴力方法:

bool isDigit(String s, int idx) {
  return s[idx] == "0"
      || s[idx] == "1"
      || s[idx] == "2"
      || s[idx] == "3"
      || s[idx] == "4"
      || s[idx] == "5"
      || s[idx] == "6"
      || s[idx] == "7"
      || s[idx] == "8"
      || s[idx] == "9";
}

0.045421617878512024微秒。

Günter的方法:

bool isDigit(String s, int idx) =>
  "0".compareTo(s[idx]) <= 0 && "9".compareTo(s[idx]) >= 0;

0.054188391470161947 纳秒

您的代码单位方法:

int _zero = "0".codeUnits[0];
int _nine = "9".codeUnits[0];
bool isDigit(String s, int idx) => 
 s.codeUnits[idx] >= _zero && s.codeUnits[idx] <= _nine;

0.6344102870896872微秒

通过存储s.codeUnits[idx]的结果(由于某种原因,虚拟机无法优化此过程,而其他方法中重复的s[idx]调用得到了优化,并且不比存储中间结果慢),可以将其提高2倍:

 bool isDigit(String s, int idx) {
     int cuIdx = s.codeUnits[idx];
     cuIdx  >= _zero && cuIdx  <= _nine;
 }

0.29245961607948817微秒

正则表达式方法:

RegExp digitRegExp = new RegExp(r'\d');
bool isDigit(String s, int idx) => s[idx].contains(digitRegExp);

4.812064808888846微秒

int.parse方法(非常缓慢):

bool isDigit(String s, int idx) {
  bool isDigit = true;
  try {
    int.parse(s[2]);
  } catch (e) {
    isDigit = false;
  }
  return isDigit;
}

102.48526774276198微秒


3

我认为这个看起来不错,只要你不使用Unicode字符。

类似的尝试,但我不认为这会更快:

bool isDigit(String s, int idx) =>
  "0".compareTo(s[idx]) <= 0 && "9".compareTo(s[idx]) >= 0;

如果编译器优化这个重复的 s[idx]/s.codeUnits[idx],那将会很有趣 - 但我认为是可以期待的。


1
你的代码有点混乱 - 这样不会返回正确的结果。两个比较都应该是 <= 0。 - Pixel Elephant
谢谢提醒,已经修正。 - Günter Zöchbauer
你是怎么推断出来的?我认为暴力尝试表明这已经被优化掉了,否则它不可能如此快速。 - Günter Zöchbauer
嗯,如果我将 s.codeUnits[idx] 的结果存储起来并且不调用两次,那么我的初始版本速度会快两倍,因此我假设虚拟机没有自动执行这项操作。 - Andrew Skalkin
我曾考虑过询问@Pixelelephant是否可以在他的基准测试中添加这样一个版本,但在看到暴力破解的结果后,我认为这是多余的。 - Günter Zöchbauer
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接