2009-06-04 18 views
5

Tôi có một chuỗi có chứa chữ số và chữ cái. Tôi muốn chia chuỗi thành các số liên tiếp của các chữ số và các khối chữ liền nhau.Trợ giúp về cách tốt hơn để phân tích cú pháp chữ số từ một Chuỗi trong Java

Xem xét chuỗi "34A312O5M444123A".

Tôi muốn đầu ra: [ "34", "A", "312", "O", "5", "M", "444.123", "A"]

Tôi có mã hoạt động và trông giống như:

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

Tôi coi chia tách hai lần để lấy một mảng chứa tất cả các khối số và một mảng chứa tất cả các khối chữ cái. Sau đó hợp nhất các kết quả. Tôi né tránh điều này vì nó sẽ gây hại cho khả năng đọc.

Tôi đã cố ý tránh giải quyết vấn đề này với mẫu regex khi tôi tìm thấy các mẫu regex là trở ngại chính cho khả năng đọc.

  • Trình gỡ rối không xử lý tốt.
  • Chúng làm gián đoạn luồng người đọc mã nguồn.
  • Thời gian làm việc của regex phát triển hữu cơ và trở thành quái vật.
  • Chúng vô cùng trực quan.

Câu hỏi của tôi là:

  • Làm sao để cải thiện khả năng đọc mã trên?
  • Có cách nào tốt hơn để thực hiện việc này không? Một lớp Util giải quyết vấn đề này một cách thanh lịch.
  • Bạn vẽ đường thẳng giữa việc sử dụng regEx và viết mã gì tương tự với những gì tôi đã viết ở trên?
  • Làm cách nào để tăng khả năng đọc/bảo trì của regExes?
+0

Lưu ý rằng mã của bạn như được viết biến chuỗi thành "[34, A, 312, O, 5, M,, N,, I, 444123, A]", đó không phải là những gì bạn nói bạn đang mong đợi . Tôi sẽ đăng mã đã sửa, nhưng Johannes Rössel đánh tôi với một câu trả lời tốt hơn. –

+0

từ, bắt tốt. –

Trả lời

5

Bạn có sẵn sàng để sử dụng regexes nếu nó có nghĩa là giải quyết các vấn đề trong một dòng mã?

// 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)"); 

Với những nhận xét để giải thích các regex, tôi nghĩ rằng đó là dễ đọc hơn so bất kỳ trong những giải pháp phi regex (hoặc bất kỳ các giải pháp regex khác, cho rằng vấn đề).

+0

+1, điều đó thực sự là khéo léo! Làm tốt lắm thưa ngài. –

13

Đối với tác vụ cụ thể này, tôi luôn sử dụng regex thay vì viết tay một cái gì đó tương tự. Mã bạn đã đưa ra ở trên là, ít nhất là với tôi, ít có thể đọc được hơn một biểu thức thông thường đơn giản (có thể là (\d+|[^\d]+) trong trường hợp này, theo như tôi thấy).

Bạn có thể muốn tránh viết biểu thức chính quy vượt quá một vài dòng. Những người có thể được và thường là không thể đọc và khó hiểu, nhưng như vậy là mã họ có thể được thay thế bằng! Trình phân tích cú pháp hầu như không bao giờ đẹp và bạn thường tốt hơn khi đọc ngữ pháp gốc hơn là cố gắng hiểu rõ về trình phân tích cú pháp được tạo (hoặc viết tay). Cùng đi (imho) cho regexes mà chỉ là một mô tả ngắn gọn về một ngữ pháp thông thường.

Vì vậy, nói chung tôi muốn nói cấm regexes ủng hộ mã như bạn đã đưa ra trong câu hỏi của bạn âm thanh như một ý tưởng khủng khiếp ngu ngốc. Và biểu thức chính quy chỉ là một công cụ, không có gì ít hơn, không có gì hơn. Nếu một cái gì đó khác làm một công việc phân tích cú pháp văn bản tốt hơn (ví dụ, một trình phân tích cú pháp thực, một số ma thuật chuỗi con, vv) sau đó sử dụng nó. Nhưng đừng vứt bỏ những khả năng chỉ vì bạn cảm thấy không thoải mái với họ - những người khác có thể gặp ít vấn đề khi đối phó với họ và tất cả mọi người đều có thể học hỏi.

EDIT: Đã cập nhật regex sau nhận xét của mmyers.

+2

+1, không phải tất cả regex đều xấu xa hoặc xấu xí. –

+0

+1, chắc chắn regEx có vị trí của mình! Vấn đề không phải là những gì regex ban đầu trông giống như, nhưng những gì regex trông giống như sau 10 người trên 5 năm đã sửa đổi nó với các trường hợp đặc biệt. Nó sẽ được gọn gàng nếu có một cái gì đó với sự sang trọng của regex, nhưng với bản chất tự tài liệu (và khả năng gỡ lỗi) của Java. –

+1

Regex phải là (\ d + | [^ \ d] +), nếu không nó sẽ lấy mọi thứ bắt đầu từ chữ số đầu tiên. Xấu hổ về bạn vì đã gây hiểu lầm cho những người thực sự đăng mã. : P –

2

Tôi sẽ sử dụng một cái gì đó như thế này (cảnh báo, mã chưa được kiểm tra). Đối với tôi, điều này có thể đọc được nhiều hơn là cố gắng tránh những suy nghĩ. Regexps là một công cụ tuyệt vời khi được sử dụng ở đúng nơi.

Phương pháp nhận xét và cung cấp ví dụ về giá trị đầu vào và đầu ra trong nhận xét cũng giúp ích.

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, ai đó đánh tôi để viết mã. Tôi nghĩ rằng phiên bản regex dễ đọc/duy trì hơn. Ngoài ra, lưu ý sự khác biệt về sản lượng giữa 2 triển khai vs sản lượng dự kiến ​​...

Output:

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] 

Hãy so sánh:

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

Giá trị mong đợi phải có MNI chứ không phải NM? –

+0

"Dự kiến" là những gì người đăng nói cho biết họ mong đợi so với việc triển khai thực hiện (digitsAsElements1) và phiên bản regex (digitsAsElements2) thực sự xuất. –

+1

Không công bằng - họ đã chỉnh sửa bài đăng :-) –

7

Đối với lớp tiện ích, hãy kiểm tra o ut java.util.Scanner. Có một số tùy chọn trong đó về cách bạn có thể giải quyết vấn đề của mình. Tôi có một vài ý kiến ​​về câu hỏi của bạn.

Debuggers không xử lý chúng (biểu thức thông thường) cũng

Cho dù một công trình regex hay không phụ thuộc vào whats trong dữ liệu của bạn. Có một số plugin tốt đẹp mà bạn có thể sử dụng để giúp bạn xây dựng một regex, như QuickREx cho Eclipse, một trình gỡ rối thực sự có giúp bạn viết trình phân tích cú pháp đúng cho dữ liệu của bạn không?

Chúng làm gián đoạn luồng người đọc mã nguồn.

Tôi đoán điều đó phụ thuộc vào mức độ thoải mái của bạn với họ. Cá nhân, tôi muốn đọc một regex hợp lý hơn 50 dòng mã phân tích chuỗi, nhưng có lẽ đó là một điều cá nhân.

Thời gian chờ của regex phát triển hữu cơ và trở thành quái vật.

Tôi đoán là có thể, nhưng đó có thể là vấn đề với mã họ đang sống để trở nên không tập trung. Nếu độ phức tạp của dữ liệu nguồn ngày càng tăng, bạn có thể cần theo dõi xem liệu bạn có cần một giải pháp biểu cảm hơn (có thể là trình tạo phân tích cú pháp như ANTLR)

Chúng rất trực quan.

Đó là ngôn ngữ phù hợp với mẫu. Tôi sẽ nói họ khá trực quan trong bối cảnh đó.

Làm cách nào để cải thiện khả năng đọc của mã ở trên?

Không chắc chắn, ngoài việc sử dụng regex.

Có cách nào tốt hơn để thực hiện việc này không? Một lớp Util giải quyết vấn đề này một cách thanh lịch.

Đã đề cập ở trên, java.util.Scanner.

Bạn vẽ đường thẳng giữa việc sử dụng regEx và viết mã gì tương tự với những gì tôi đã viết ở trên?

Cá nhân tôi sử dụng regex cho mọi thứ đơn giản một cách hợp lý.

Làm cách nào để tăng khả năng đọc/bảo trì của regExes?

Suy nghĩ cẩn thận trước khi mở rộng, cẩn thận hơn để nhận xét mã và chi tiết regex để rõ ràng bạn đang làm gì.

+0

Chà, tôi phải mất nhiều thời gian để viết bài này! Đã có rất nhiều câu trả lời trong thời gian chờ đợi, xin lỗi nếu tôi trùng lặp. – Brabster

+0

+1, tốt lắm! Đây chính xác là câu trả lời tôi đang tìm kiếm, tôi ước tôi có thể cung cấp cho bạn 10 điểm. = D –

+0

Rất vui được giúp đỡ. Chúc may mắn! – Brabster

1

bạn có thể sử dụng lớp này để đơn giản hóa vòng lặp của bạn:

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

Bây giờ bạn có thể viết lại này:

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

với:

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

2 cent tôi

BTW clas này s cũng có thể tái sử dụng trong ngữ cảnh khác.

+0

+1, StringIterator trông khá gọn gàng. –

+1

Mặc dù vậy, quy mô không quy mô. Mỗi ký tự phải được đóng hộp cho Iterator , sau đó được mở hộp cho vòng lặp foreach; hiệu suất giết mổ đó. –

+0

bạn nói đúng. Tôi đã cố định ít nhất là unboxing trong vòng lặp – dfa

1

Tôi không quá điên rồ về regex bản thân mình, nhưng điều này có vẻ như một trường hợp mà họ thực sự sẽ đơn giản hóa mọi thứ. Những gì bạn có thể muốn làm là đặt chúng vào phương pháp nhỏ nhất mà bạn có thể đưa ra, đặt tên nó một cách thích hợp, và sau đó đặt tất cả mã điều khiển trong một phương thức khác. Ví dụ, nếu bạn mã hóa một phương pháp "Grab khối của chữ số hoặc chữ", người gọi sẽ là một vòng lặp rất đơn giản, thẳng về phía trước chỉ in kết quả của mỗi cuộc gọi, và phương pháp bạn đang gọi sẽ là tốt -định nghĩa vì vậy ý ​​định của regex sẽ rõ ràng ngay cả khi bạn không biết gì về cú pháp, và phương pháp sẽ bị ràng buộc để mọi người không thể nhúc nhích nó theo thời gian.

Vấn đề với điều này là các công cụ regex rất đơn giản và phù hợp với việc sử dụng này, thật khó để biện minh cho một cuộc gọi phương thức cho việc này.

1

Vì dường như chưa có ai đăng đúng mã, tôi sẽ cung cấp cho nó một shot.

Đầu tiên là phiên bản không phải regex. Lưu ý rằng tôi sử dụng StringBuilder để tích lũy bất kỳ loại ký tự nào được nhìn thấy lần cuối (chữ số hoặc chữ số). Nếu trạng thái thay đổi, tôi đổ nội dung của nó vào danh sách và bắt đầu một StringBuilder mới. Bằng cách này, các chữ số không liên tiếp được nhóm lại giống như các chữ số liên tiếp.

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

Bây giờ, phiên bản regex. Điều này về cơ bản là cùng một mã được đăng bởi Juha S., nhưng regex thực sự hoạt động.

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

Một cách tôi cố gắng giữ cho regex có thể đọc được là tên của chúng.Tôi nghĩ rằng DIGIT_OR_NONDIGIT_STRING truyền tải khá tốt những gì tôi (các lập trình viên) nghĩ rằng nó, và thử nghiệm nên đảm bảo rằng nó thực sự làm những gì nó có nghĩa là để làm.

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

in:

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