2009-06-04 9 views
5

У меня есть строка, которая содержит цифры и буквы. Я хочу разбить строку на смежные куски цифр и смежные куски букв.Помогите лучше разбирать цифры из строки в Java

Рассмотрите строку "34A312O5M444123A".

Я хотел бы выход: [ "34", "A", "312", "O", "5", "М", "444123", "А"]

меня код, который работает и выглядит следующим образом:

List<String> digitsAsElements(String str){ 
    StringBuilder digitCollector = new StringBuilder(); 

    List<String> output = new ArrayList<String>(); 

    for (int i = 0; i < str.length(); i++){ 
    char cChar = str.charAt(i); 

    if (Character.isDigit(cChar)) 
     digitCollector.append(cChar); 
    else{ 
     output.add(digitCollector.toString()); 
     output.add(""+cChar); 

     digitCollector = new StringBuilder(); 
    }   
    } 

    return output; 
} 

Я считал ул расщепляющий дважды, чтобы получить массив, содержащий все числа ломти и массив, содержащий все буквы куски. Затем слияние результатов. Я уклонился от этого, так как это навредило бы читаемости.

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

  • Отладчики плохо справляются с ними.
  • Они прерывают поток кого-то, читающего исходный код.
  • Регулярное выражение регулярного выражения Overtime вырастает органично и становится монстрами.
  • Они глубоко не интуитивно понятны.

Мои вопросы:

  • Как я мог улучшить читаемость кода выше?
  • Есть ли лучший способ сделать это? Класс Util, который решает эту проблему элегантно.
  • Где вы рисуете линию между использованием regEx и кодированием чего-то близкого к тому, что я написал выше?
  • Как повысить читабельность/поддерживаемость regExes?
+0

Обратите внимание, что ваш код в письменном виде превращает строку в «[34, A, 312, O, 5, M,, N,, I, 444123, A]», это не то, что вы говорите, что ожидаете , Я собирался опубликовать исправленный код, но Йоханнес Рёссел избил меня лучшим ответом. –

+0

слово, хороший catch. –

ответ

5

ли вы быть готовы использовать регулярные выражения, если это означает решение проблемы в одной строке кода?

// Split at any position that's either: 
// preceded by a digit and followed by a non-digit, or 
// preceded by a non-digit and followed by a digit. 
String[] parts = str.split("(?<=\\d)(?=\\D)|(?<=\\D)(?=\\d)"); 

С комментарием объяснить регулярное выражение, я думаю, что это более читаемым, чем любой из не регулярных выражений решений (или любой из других регулярных выражений решений по этому вопросу).

+0

+1, это действительно пятно! Молодец, сэр. –

13

Для этой конкретной задачи я всегда использую регулярное выражение вместо того, чтобы писать что-то подобное. Код, который вы указали выше, по крайней мере для меня менее читабельен, чем простое регулярное выражение (которое в этом случае было бы (\d+|[^\d]+), насколько я вижу).

Возможно, вы захотите избежать написания регулярных выражений, которые превышают несколько строк. Это могут быть и обычно нечитаемы и трудно понять, , но так же, как и код, который они могут заменить! Парсеры почти никогда не красивы, и вам обычно лучше читать оригинальную грамматику, чем пытаться понять созданный (или рукописный) парсер. То же самое происходит (imho) для регулярных выражений, которые являются лишь кратким описанием регулярной грамматики.

Итак, в общем, я бы сказал, что запрещение регулярных выражений в пользу кода, как вы дали в своем вопросе, звучит как ужасно глупая идея. И регулярные выражения - это просто инструмент, не меньше, не более того. Если что-то еще лучше выполняет синтаксический анализ текста (скажем, настоящий парсер, магия подстроки и т. Д.), То используйте его. Но не выбрасывайте возможности только потому, что вам неловко с ними - у других может быть меньше проблем с ними, и все люди могут учиться.

EDIT: Обновлено regex после комментария от mmyers.

+2

+1, не все регулярное выражение является злым или уродливым. –

+0

+1, конечно, у RegEx есть свое место! Проблема заключается не в том, как выглядит исходное регулярное выражение, а в том, как выглядит регулярное выражение после того, как 10 человек в течение 5 лет внесли поправки в него в особых случаях. Это было бы аккуратно, если бы было что-то с элегантностью регулярного выражения, но с самоописывающей природой (и отлаживаемостью) Java. –

+1

Регулярное выражение должно быть (\ d + | [^ \ d] +), иначе оно будет захватывать все, начиная с первой не-цифры. Позор вам, чтобы ввести в заблуждение людей, которые действительно опубликовали код. : P –

2

Я бы использовал что-то вроде этого (предупреждение, непроверенный код). Для меня это намного более читаемо, чем попытка избежать регулярных выражений. Regexps - отличный инструмент, когда он используется в правильном месте.

Комментирование методов и предоставление примеров входных и выходных значений в комментариях также помогает.

List<String> digitsAsElements(String str){ 
    Pattern p = Pattern.compile("(\\d+|\\w+)*"); 
    Matcher m = p.matcher(str); 

    List<String> output = new ArrayList<String>(); 
    for(int i = 1; i <= m.groupCount(); i++) { 
     output.add(m.group(i)); 
    } 
    return output; 
} 
1

Awww, кто-то избил меня в код. Я думаю, что регулярное выражение легче читать/поддерживать. Кроме того, обратите внимание на разницу в выходе между 2 реализациями против ожидаемого результата ...

Выход:

digitsAsElements1("34A312O5MNI444123A") = [34, A, 312, O, 5, M, , N, , I, 444123, A] 
digitsAsElements2("34A312O5MNI444123A") = [34, A, 312, O, 5, MNI, 444123, A] 
Expected: [34, A, 312, O, 5, MN, 444123, A] 

Сравнить:

DigitsAsElements.java:

import java.util.ArrayList; 
import java.util.List; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 

public class DigitsAsElements { 

    static List<String> digitsAsElements1(String str){ 
     StringBuilder digitCollector = new StringBuilder(); 

     List<String> output = new ArrayList<String>(); 

     for (int i = 0; i < str.length(); i++){ 
      char cChar = str.charAt(i); 

      if (Character.isDigit(cChar)) 
      digitCollector.append(cChar); 
      else{ 
      output.add(digitCollector.toString()); 
      output.add(""+cChar); 

      digitCollector = new StringBuilder(); 
      }   
     } 

     return output; 
     } 

    static List<String> digitsAsElements2(String str){ 
     // Match a consecutive series of digits or non-digits 
     final Pattern pattern = Pattern.compile("(\\d+|\\D+)"); 
     final Matcher matcher = pattern.matcher(str); 

     final List<String> output = new ArrayList<String>(); 
     while (matcher.find()) { 
      output.add(matcher.group()); 
     } 

     return output; 
     } 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     System.out.println("digitsAsElements(\"34A312O5MNI444123A\") = " + 
       digitsAsElements1("34A312O5MNI444123A")); 
     System.out.println("digitsAsElements2(\"34A312O5MNI444123A\") = " + 
       digitsAsElements2("34A312O5MNI444123A")); 
     System.out.println("Expected: [" + 
       "34, A, 312, O, 5, MN, 444123, A"+"]"); 
    } 

} 
+0

Ожидаемое значение должно иметь MNI, а не NM? –

+0

«Ожидаемый» - это то, что плакат сказал, что они ожидали от того, что их реализация (digitsAsElements1) и версия регулярного выражения (digitsAsElements2) действительно выводятся. –

+1

Нет, честно говоря, они отредактировали сообщение :-) –

7

Для класса полезности, проверьте o ut java.util.Scanner. Есть несколько вариантов в том, как вы можете решить свою проблему. У меня есть несколько комментариев по вашим вопросам.

Debuggers не обрабатывать их (регулярные выражения) ну

ли регулярных выражений работает или нет, зависит от, что там в ваших данных. Есть несколько полезных плагинов, которые вы можете использовать, чтобы помочь вам создать регулярное выражение, например QuickREx для Eclipse, действительно ли отладчик поможет вам написать правильный парсер для ваших данных?

Они прерывают поток кого-то, читающего исходный код.

Я думаю, это зависит от того, насколько вам комфортно с ними. Лично я предпочел бы прочитать разумное регулярное выражение, кроме 50 строк строкового кода синтаксического анализа, но, возможно, это личное дело.

Сверхудлинное регулярное выражение вырастает органично и становится монстрами.

Я думаю, они могут, но это, вероятно, проблема с кодом, в котором они живут, и не сосредоточиться. Если сложность исходных данных увеличивается, вы, вероятно, нужно следить, нужно ли более выразительным решение (возможно, генератор синтаксических анализаторов, как ANTLR)

Они глубоко не интуитивным.

Это язык, соответствующий шаблону. Я бы сказал, что они довольно интуитивно понятны в этом контексте.

Как я мог улучшить читаемость вышеуказанного кода?

Не уверен, кроме использования регулярного выражения.

Есть ли лучший способ сделать это? Класс Util, который решает эту проблему элегантно.

Упоминается выше, java.util.Scanner.

Где вы рисуете линию между использованием regEx и кодированием чего-то похожего на то, что я написал выше?

Лично я использую регулярное выражение для чего-либо достаточно простого.

Как повысить читаемость/ремонтопригодность регистров?

Подумайте внимательно, прежде чем расширять, проявите особую осторожность, чтобы подробно комментировать код и регулярное выражение, чтобы было ясно, что вы делаете.

+0

Ничего себе, должно быть, мне понадобилось много времени, чтобы написать это! Между тем была нагрузка ответов, извините, если я перекрываю. – Brabster

+0

+1, молодец! Это именно тот ответ, который я искал, я бы хотел дать вам +10. = D –

+0

Очень рад помочь. Удачи! – Brabster

1

вы могли бы использовать этот класс для того, чтобы упростить цикл:

public class StringIterator implements Iterator<Character> { 

    private final char[] chars; 
    private int i; 

    private StringIterator(char[] chars) { 
     this.chars = chars; 
    } 

    public boolean hasNext() { 
     return i < chars.length; 
    } 

    public Character next() { 
     return chars[i++]; 
    } 

    public void remove() { 
     throw new UnsupportedOperationException("Not supported."); 
    } 

    public static Iterable<Character> of(String string) { 
     final char[] chars = string.toCharArray(); 

     return new Iterable<Character>() { 

      @Override 
      public Iterator<Character> iterator() { 
       return new StringIterator(chars); 
      } 
     }; 
    } 
} 

Теперь вы можете переписать так:

for (int i = 0; i < str.length(); i++){ 
    char cChar = str.charAt(i); 
    ... 
} 

с:

for (Character cChar : StringIterator.of(str)) { 
    ... 
} 

мои 2 цента

BTW Этот раздел s также может использоваться повторно в другом контексте.

+0

+1, StringIterator выглядит довольно аккуратно. –

+1

Он не масштабируется. Каждый символ должен быть помещен в ящик для Iterator , затем распакован для цикла foreach; что снижает производительность. –

+0

Ты прав. Я зафиксировал по крайней мере unboxing в цикле for – dfa

1

Я не слишком сумасшедший по поводу регулярного выражения, но это похоже на случай, когда они действительно упростят ситуацию. То, что вы, возможно, захотите сделать, это поместить их в самый маленький метод, который вы можете разработать, назвать его метко, а затем поместить весь управляющий код в другой метод.

Например, если вы закодировали метод «Grab block of numbers или letters», вызывающий объект был бы очень простым, прямолинейным, просто распечатывая результаты каждого вызова, и метод, который вы вызывали, был бы хорошо -defined, так что намерение регулярного выражения было бы ясным, даже если бы вы ничего не знали о синтаксисе, и метод был бы ограничен, поэтому люди вряд ли могли бы его испортить с течением времени.

Проблема заключается в том, что инструменты регулярных выражений настолько просты и хорошо адаптированы к этому использованию, что для этого трудно обосновать вызов метода.

1

Поскольку никто, кажется, не разместил правильный код, я дам ему выстрел.

Первая версия без регулярного выражения. Обратите внимание, что я использую StringBuilder для накопления в зависимости от того, какой тип символа был замечен последним (цифрой или без цифр). Если состояние изменяется, я выгружаю его содержимое в список и запускаю новый StringBuilder. Таким образом, последовательные не-цифры группируются так же, как и последовательные цифры.

static List<String> digitsAsElements(String str) { 
    StringBuilder collector = new StringBuilder(); 

    List<String> output = new ArrayList<String>(); 
    boolean lastWasDigit = false; 
    for (int i = 0; i < str.length(); i++) { 
     char cChar = str.charAt(i); 

     boolean isDigit = Character.isDigit(cChar); 
     if (isDigit != lastWasDigit) { 
      if (collector.length() > 0) { 
       output.add(collector.toString()); 
       collector = new StringBuilder(); 
      } 
      lastWasDigit = isDigit; 
     } 
     collector.append(cChar); 
    } 
    if (collector.length() > 0) 
     output.add(collector.toString()); 

    return output; 
} 

Теперь версия с регулярным выражением. Это в основном тот же код, который был опубликован Juha S., но регулярное выражение действительно работает.

private static final Pattern DIGIT_OR_NONDIGIT_STRING = 
     Pattern.compile("(\\d+|[^\\d]+)"); 
static List<String> digitsAsElementsR(String str) { 
    // Match a consecutive series of digits or non-digits 
    final Matcher matcher = DIGIT_OR_NONDIGIT_STRING.matcher(str); 
    final List<String> output = new ArrayList<String>(); 
    while (matcher.find()) { 
     output.add(matcher.group()); 
    } 
    return output; 
} 

Один из способов, с помощью которых я пытаюсь сохранить свои регулярные выражения, является их именами.Я думаю, что DIGIT_OR_NONDIGIT_STRING очень хорошо передает, что я (программист) думаю, что он это делает, и тестирование должно убедиться, что оно действительно делает то, что он должен делать.

public static void main(String[] args) { 
    System.out.println(digitsAsElements("34A312O5MNI444123A")); 
    System.out.println(digitsAsElementsR("34A312O5MNI444123A")); 
} 

печатает:

 
[34, A, 312, O, 5, MNI, 444123, A] 
[34, A, 312, O, 5, MNI, 444123, A]