Как преобразовать буквенно-цифровой номер телефона в цифры

обновление:

окончательная версия моей утилиты выглядит так:

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. Моей первой попыткой было создать неприятный оператор switch, но я бы хотел более элегантное и эффективное решение. Вот что я got:

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 ответов


оператор switch на самом деле не так уж плох. Ваш алгоритм является линейным по отношению к длине номер телефона. Код читаем и довольно легко проверить с помощью проверки. Я бы не стал возиться с ним, кроме как добавить default случае для обработки ошибок. (Я не программист Java, поэтому простите меня, если это называется чем-то другим.)

Если вы есть чтобы сделать его быстрее, предварительно инициализированная таблица, индексированная символом, избегала бы любых сравнений за пределами basic проверка ошибок. Вы даже можете избежать преобразования case, дублируя значения в таблице (digit['A'] = digit['a'] = "2";). Расходы на инициализацию таблицы будут амортизироваться по сравнению с общим числом конверсий.


вы можете сделать это, используя Apache Commons Lang StringUtils, следующим образом:

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

предполагая, что скорость не является вашей главной заботой, конечно, и вы хотите компактное решение;)


использовать 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 инструкции). Это было просто для удовольствия, заметьте, но на моем ноутбуке, работающем в одном потоке, я мог бы преобразовать 10 миллионов 10-буквенных"чисел" примерно за 1,3 секунды. Я был очень удивлен, потому что, как я понимаю,tableswitch работает по существу таким же образом, но я ожидал, что это будет быстрее, так как это инструкция JVM.

конечно, если только мне не платили только за каждый из неограниченный запас телефонных номеров, которые я мог бы преобразовать, я бы никогда не написал такой код. Коммутатор гораздо более удобочитаем, хорошо работает как есть и, вероятно, получит бесплатное повышение производительности в какой-то будущей JVM.

далеко и далеко, наибольшее улучшение исходного кода происходит от использования StringBuilder вместо объединения строк, и это ничего не делает для ухудшения читаемости кода. Используя charAt вместо преобразования входных данных char[] также делает код более простым и легче понять и тоже повышает производительность. Наконец, добавление char литералы вместо String литералы ('1', а не "1") - это улучшение производительности, которое также немного помогает читаемости.


Как насчет просто:

   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;
   }

Если вы хотите решение, которое не заставляет вас перечислять все буквы, вы можете сделать что-то вроде:

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;

Если вы запустите это 10^9 раз в узком цикле и ctrl-break несколько раз, я держу пари, что почти каждый раз он будет глубоко в классе строк, пытаясь выполнить один из этих невинных операторов"+=".


операторы Switch компилируются в аналогичную форму, как если бы-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, 'а' = 0x61, и 'Z' = 0x7a.

Edit: лучше использовать StringBuilder и append() чтобы создать строку, но для небольших строк это не вероятно, будет заметно быстрее. ( закон Амдала демонстрирует, что фактическое ускорение, которое вы можете ожидать от оптимизации кода, ограничено процентом времени, фактически потраченного в этом коде.) Я использовал только сцепленные строки, чтобы сделать алгоритм понятным для OP.