如何将字母数字电话号码转换为数字

时间:2009-09-22 21:51:25

标签: java performance algorithm switch-statement

更新

我的实用程序的最终版本如下所示:

StringBuilder b = new StringBuilder();

for(char c : inLetters.toLowerCase().toCharArray())
{
    switch(c)
    {
    case '0':                                          b.append("0"); break;
    case '1':                                          b.append("1"); break;
    case '2': case 'a': case 'b': case 'c':            b.append("2"); break;
    case '3': case 'd': case 'e': case 'f':            b.append("3"); break;
    case '4': case 'g': case 'h': case 'i':            b.append("4"); break;
    case '5': case 'j': case 'k': case 'l':            b.append("5"); break;
    case '6': case 'm': case 'n': case 'o':            b.append("6"); break;
    case '7': case 'p': case 'q': case 'r': case 's':  b.append("7"); break;
    case '8': case 't': case 'u': case 'v':            b.append("8"); break;
    case '9': case 'w': case 'x': case 'y': case 'z':  b.append("9"); break;
    }
}

return builder.toString();

原始问题:

我正在承担将字母数字电话号码转换为数字字符串的简单任务。例如,1-800-HI-HAXOR将变为1-800-44-42967。我最初的尝试是创建一个讨厌的开关声明,但我喜欢一个更优雅,更有效的解决方案。这就是我所拥有的:

for(char c : inLetters.toLowerCase().toCharArray())
{
    switch(c)
    {
    case '0':                                         result+="0"; break;
    case '1':                                         result+="1"; break;
    case '2': case 'a': case 'b': case 'c':           result+="2"; break;
    case '3': case 'd': case 'e': case 'f':           result+="3"; break;
    case '4': case 'g': case 'h': case 'i':           result+="4"; break;
    case '5': case 'j': case 'k': case 'l':           result+="5"; break;
    case '6': case 'm': case 'n': case 'o':           result+="6"; break;
    case '7': case 'p': case 'q': case 'r': case 's': result+="7"; break;
    case '8': case 't': case 'u': case 'v':           result+="8"; break;
    case '9': case 'w': case 'x': case 'y': case 'z': result+="9"; break;
    }
}

谢谢!

7 个答案:

答案 0 :(得分:9)

switch语句并不是那么糟糕。您的算法与电话号码的长度成线性关系。代码是可读的,很容易通过检查验证。除了为处理错误添加default案例外,我不会搞乱它。 (我不是Java程序员,如果它被称为其他东西,请原谅我。)

如果 使其更快,则按字符索引的预初始化表将避免除基本错误检查之外的任何比较。您甚至可以通过复制表中的值(digit['A'] = digit['a'] = "2";)来避免大小写转换。初始化表格的费用将按总转化次数摊销。

答案 1 :(得分:6)

您可以使用Apache Commons Lang StringUtils执行此操作,如下所示:

String output = StringUtils.replaceChars(StringUtils.lowerCase(input),
                    "abcdefghijklmnopqrstuvwxyz",
                    "22233344455566677778889999");

当然,假设速度不是您的主要考虑因素,而您需要一个紧凑的解决方案;)

答案 2 :(得分:5)

使用Map,其中键是字母和数字,值是键盘上的数字。 (因此每个键盘编号将被三个或四个字母和一个数字编入索引)。

Map<Character, Character> keypad = new HashMap<Character, Character>();
...
StringBuilder buf = new StringBuilder(inLetters.length());
for (int idx = 0; idx < inLetters.length(); ++idx) {
  Character ch = keypad.get(inLetters.charAt(idx));
  if (ch != null)
    buf.append(ch);
}

更新:我很好奇手动编码的查找表是否比密集的switch个案例表现更好。在我的临时测试中,我发现以下代码是我能想到的最快的代码:

  private static final char[] lut = 
    "0123456789:;<=>?@22233344455566677778889999[\\]^_`22233344455566677778889999".toCharArray();

  private static final char min = lut[0];

  String fastest(String letters)
  {
    int n = letters.length();
    char[] buf = new char[n];
    while (n-- > 0) {
      int ch = letters.charAt(n) - min;
      buf[n] = ((ch < 0) || (ch >= lut.length)) ? letters.charAt(n) : lut[ch];
    }
    return new String(buf);
  }

令人惊讶的是,它的速度是使用switch语句(编译为tableswitch指令)的类似代码的两倍多。这只是为了好玩,请注意,但在我的笔记本电脑上,在一个线程中运行,我可以在大约1.3秒内转换1000万个10个字母的“数字”。我真的很惊讶,因为根据我的理解,tableswitch以基本相同的方式运行,但我预计它会更快,因为它是一个JVM指令。

当然,除非我只为每一个我可以转换的无限电话号码获得报酬,否则我绝不会写这样的代码。交换机更具可读性,运行良好,并且可能在未来的某些JVM中获得免费的性能提升。

远远地,对原始代码的最大改进来自于使用StringBuilder而不是连接字符串,并且这不会影响代码的可读性。使用charAt而不是将输入转换为char[]也可以使代码更简单,更易于理解也可以提高性能。最后,添加char文字而不是String文字('1'而不是"1")是一种性能改进,有助于提高可读性。

答案 3 :(得分:4)

简单地说:

   String convert(String inLetters) {
      String digits = "22233344455566677778889999";
      String alphas = "abcdefghijklmnopqrstuvwxyz";
      String result = "";
      for (char c : inLetters.toLowerCase().toCharArray()) {
          int pos = alphas.indexOf(c);
          result += (pos == -1 ? c : digits.charAt(pos));
      }
      return result;
   }

答案 4 :(得分:2)

如果你想要一个不会强迫你枚举所有字母的解决方案,你可以这样做:

char convertedChar = c;
if (Character.isLetter(c)) {
    //lowercase alphabet ASCII codes: 97 (a)-122 (z)
    int charIndex = ((int)c) - 97;
    //make adjustments to account for 's' and 'z'
    if (charIndex >= 115) { //'s'
        charIndex--;
    }
    if (charIndex == 121) { //'z'-1
        charIndex--;
    }
    convertedChar = (char)(2 + (charIndex/3));
}
result += convertedChar;

答案 5 :(得分:1)

如果你在一个紧凑的循环中运行这个10 ^ 9次并且按住它几次,我的赌注是几乎每次它都会在字符串类中深入尝试完成其中一个无辜的“+ =“运营商。

答案 6 :(得分:0)

将switch语句编译为与if-else语句类似的形式,(每个case语句基本上是伪装的 if (c == '...') 测试),所以虽然这在视觉上更多如果对每个角色进行测试,则比级联紧凑,可能有也可能没有任何真正的性能优势。

您可以通过消除一些比较来简化它。关键是char是一个整数类型(这就是为什么你可以打开char)所以你可以使用数字比较运算符。并且'aAssuming你的inLetters字符串只包含字母数字字符,这应该有效...(所有其他字符将保持不变。)

String result = "";
for (char c : letters.toLowerCase().toCharArray()) {
    if      (c <= '9') result += c;
    else if (c <= 'c') result += "2";
    else if (c <= 'f') result += "3";
    else if (c <= 'i') result += "4";
    else if (c <= 'l') result += "5";
    else if (c <= 'o') result += "6";
    else if (c <= 's') result += "7";
    else if (c <= 'v') result += "8";
    else if (c <= 'z') result += "9";
    else               result += c;
}

感兴趣的字符具有十六进制值:'0'= 0x30,'9'= 0x39,'a'= 0x61,'z'= 0x7a。

编辑:最好使用StringBuilderappend()来创建字符串,但对于小字符串,它的速度可能不会快得多。 (Amdahl's Law表明优化代码可以获得的实际加速比率受到该代码实际花费的时间百分比的限制。)我只使用连接字符串使算法清楚地显示在OP中。