2014-04-29 31 views
6

Tổng quanChia Chuỗi tại ngôn ngữ tự nhiên phá vỡ

tôi gửi Strings đến một máy chủ Text-to-Speech chấp nhận chiều dài tối đa 300 ký tự. Do độ trễ mạng, có thể có sự chậm trễ giữa mỗi phần của lời nói được trả lại, vì vậy tôi muốn chia nhỏ bài phát biểu ở hầu hết các 'tạm dừng tự nhiên' ở bất kỳ nơi nào có thể.

Mỗi yêu cầu của máy chủ đều tốn tiền cho tôi, vì vậy, lý tưởng nhất là tôi gửi chuỗi dài nhất có thể, tối đa các ký tự được phép tối đa.

Đây là thực hiện hiện tại của tôi:

private static final boolean DEBUG = true; 

private static final int MAX_UTTERANCE_LENGTH = 298; 
private static final int MIN_UTTERANCE_LENGTH = 200; 

private static final String FULL_STOP_SPACE = ". "; 
private static final String QUESTION_MARK_SPACE = "? "; 
private static final String EXCLAMATION_MARK_SPACE = "! "; 
private static final String LINE_SEPARATOR = System.getProperty("line.separator"); 
private static final String COMMA_SPACE = ", "; 
private static final String JUST_A_SPACE = " "; 

public static ArrayList<String> splitUtteranceNaturalBreaks(String utterance) { 

    final long then = System.nanoTime(); 

    final ArrayList<String> speakableUtterances = new ArrayList<String>(); 

    int splitLocation = 0; 
    String success = null; 

    while (utterance.length() > MAX_UTTERANCE_LENGTH) { 

     splitLocation = utterance.lastIndexOf(FULL_STOP_SPACE, MAX_UTTERANCE_LENGTH); 

     if (DEBUG) { 
      System.out.println("(0 FULL STOP) - last index at: " + splitLocation); 
     } 

     if (splitLocation < MIN_UTTERANCE_LENGTH) { 
      if (DEBUG) { 
       System.out.println("(1 FULL STOP) - NOT_OK"); 
      } 

      splitLocation = utterance.lastIndexOf(QUESTION_MARK_SPACE, MAX_UTTERANCE_LENGTH); 

      if (DEBUG) { 
       System.out.println("(1 QUESTION MARK) - last index at: " + splitLocation); 
      } 

      if (splitLocation < MIN_UTTERANCE_LENGTH) { 
       if (DEBUG) { 
        System.out.println("(2 QUESTION MARK) - NOT_OK"); 
       } 

       splitLocation = utterance.lastIndexOf(EXCLAMATION_MARK_SPACE, MAX_UTTERANCE_LENGTH); 

       if (DEBUG) { 
        System.out.println("(2 EXCLAMATION MARK) - last index at: " + splitLocation); 
       } 

       if (splitLocation < MIN_UTTERANCE_LENGTH) { 
        if (DEBUG) { 
         System.out.println("(3 EXCLAMATION MARK) - NOT_OK"); 
        } 

        splitLocation = utterance.lastIndexOf(LINE_SEPARATOR, MAX_UTTERANCE_LENGTH); 

        if (DEBUG) { 
         System.out.println("(3 SEPARATOR) - last index at: " + splitLocation); 
        } 

        if (splitLocation < MIN_UTTERANCE_LENGTH) { 
         if (DEBUG) { 
          System.out.println("(4 SEPARATOR) - NOT_OK"); 
         } 

         splitLocation = utterance.lastIndexOf(COMMA_SPACE, MAX_UTTERANCE_LENGTH); 

         if (DEBUG) { 
          System.out.println("(4 COMMA) - last index at: " + splitLocation); 
         } 

         if (splitLocation < MIN_UTTERANCE_LENGTH) { 
          if (DEBUG) { 
           System.out.println("(5 COMMA) - NOT_OK"); 
          } 

          splitLocation = utterance.lastIndexOf(JUST_A_SPACE, MAX_UTTERANCE_LENGTH); 

          if (DEBUG) { 
           System.out.println("(5 SPACE) - last index at: " + splitLocation); 
          } 

          if (splitLocation < MIN_UTTERANCE_LENGTH) { 
           if (DEBUG) { 
            System.out.println("(6 SPACE) - NOT_OK"); 
           } 

           splitLocation = MAX_UTTERANCE_LENGTH; 

           if (DEBUG) { 
            System.out.println("(6 MAX_UTTERANCE_LENGTH) - last index at: " + splitLocation); 
           } 

          } else { 
           if (DEBUG) { 
            System.out.println("Accepted"); 
           } 

           splitLocation -= 1; 
          } 
         } 
        } else { 
         if (DEBUG) { 
          System.out.println("Accepted"); 
         } 

         splitLocation -= 1; 
        } 
       } else { 
        if (DEBUG) { 
         System.out.println("Accepted"); 
        } 
       } 
      } else { 
       if (DEBUG) { 
        System.out.println("Accepted"); 
       } 
      } 
     } else { 
      if (DEBUG) { 
       System.out.println("Accepted"); 
      } 
     } 

     success = utterance.substring(0, (splitLocation + 2)); 

     speakableUtterances.add(success.trim()); 

     if (DEBUG) { 
      System.out.println("Split - Length: " + success.length() + " -:- " + success); 
      System.out.println("------------------------------"); 
     } 

     utterance = utterance.substring((splitLocation + 2)).trim(); 
    } 

    speakableUtterances.add(utterance); 

    if (DEBUG) { 

     System.out.println("Split - Length: " + utterance.length() + " -:- " + utterance); 

     final long now = System.nanoTime(); 
     final long elapsed = now - then; 

     System.out.println("ELAPSED: " + TimeUnit.MILLISECONDS.convert(elapsed, TimeUnit.NANOSECONDS)); 

    } 

    return speakableUtterances; 
} 

Nó xấu xí do không có khả năng sử dụng regex trong lastIndexOf. Xấu xí sang một bên, nó thực sự khá nhanh.

vấn đề

Lý tưởng nhất là tôi muốn sử dụng regex cho phép cho một trận đấu trên một trong delimiters lựa chọn đầu tiên của tôi:

private static final String firstChoice = "[.!?" + LINE_SEPARATOR + "]\\s+"; 
private static final Pattern pFirstChoice = Pattern.compile(firstChoice); 

Và sau đó sử dụng một khớp để giải quyết các vị trí:

Matcher matcher = pFirstChoice.matcher(input); 

    if (matcher.find()) { 
     splitLocation = matcher.start(); 
    } 

Cách thay thế hiện tại của tôi là lưu trữ vị trí của từng dấu phân cách và sau đó chọn vị trí gần nhất MAX_UTTERANCE_LENGTH

Tôi đã thử phương pháp khác nhau để áp dụng các MIN_UTTERANCE_LENGTH & MAX_UTTERANCE_LENGTH đến Pattern, vì vậy nó chỉ chụp giữa các giá trị và sử dụng lookarounds để đảo ngược lặp ?<=, nhưng đây là nơi kiến ​​thức của tôi bắt đầu thất bại tôi:

private static final String poorEffort = "([.!?]{200, 298})\\s+");

Cuối cùng

tôi tự hỏi nếu có của bạn chủ regex có thể đạt được những gì tôi sau và con công ty nếu trong thực tế thực tế, nó sẽ chứng minh hiệu quả hơn?

Tôi cảm ơn bạn trước.

Tài liệu tham khảo:

+3

Câu hỏi hay, tuyệt vời. Tôi hy vọng sẽ thấy một số câu trả lời đến sớm vì đây có vẻ là loại thách thức chính xác mà các chàng trai 'regex' thích. – MattSizzle

Trả lời

1

tôi sẽ làm một cái gì đó như thế này:

Pattern p = Pattern.compile(".{1,299}(?:[.!?]\\s+|\\n|$)", Pattern.DOTALL); 
Matcher matcher = p.matcher(text); 
while (matcher.find()) { 
    speakableUtterances.add(matcher.group().trim()); 
} 

Exp lanation của regex:

.{1,299}     any character between 1 and 299 times (matching the most amount possible) 
(?:[.!?]\\s+|\\n|$)  followed by either .!? and whitespaces, a newline or the end of the string 

Bạn có thể xem xét để mở rộng dấu chấm câu để \p{Punct}, xem javadoc cho Pattern.

Bạn có thể xem mẫu đang hoạt động trên ideone.

+0

Thật tuyệt vời! Cực kỳ nhanh chóng, cảm ơn bạn. Vấn đề duy nhất là độ dài tối thiểu phù hợp 200 ký tự. Tôi nghĩ cách duy nhất tôi có thể đạt được đó là kiểm tra độ dài của 'nhóm()' và quay trở lại với các lựa chọn thứ hai của tôi, sử dụng một mô hình thay thế? – brandall

+0

Vâng, tôi đoán nếu bạn muốn quay trở lại bạn sẽ cần phải làm một cái gì đó như thế. Nhưng nó trở nên phức tạp hơn khi bạn cần điều chỉnh Matcher khác. Bạn có thể sử dụng 'find (index)', 'start()' và 'end()'. – morja

0

Unicode standard xác định cách bạn nên chia văn bản thành câu và các thành phần logic khác. Dưới đây là một số giả làm việc:

// tests two consecutive codepoints within the text to detect the end of sentences 
boolean continueSentence(Text text, Range range1, Range range2) { 
    Code code1 = text.code(range1), code2 = text.code(range2); 

    // 0.2 sot ÷ 
    if (code1.isStartOfText()) 
     return false; 

    // 0.3  ÷ eot 
    if (code2.isEndOfText()) 
     return false; 

    // 3.0 CR × LF 
    if (code1.isCR() && code2.isLF()) 
     return true; 

    // 4.0 (Sep | CR | LF) ÷ 
    if (code1.isSep() || code1.isCR() || code1.isLF()) 
     return false; 

    // 5.0  × [Format Extend] 
    if (code2.isFormat() || code2.isExtend()) 
     return true; 

    // 6.0 ATerm × Numeric 
    if (code1.isATerm() && (code2.isDigit() || code2.isDecimal() || code2.isNumeric())) 
     return true; 

    // 7.0 Upper ATerm × Upper 
    if (code2.isUppercase() && code1.isATerm()) { 
     Range range = text.previousCode(range1); 
     if (range.isValid() && text.code(range).isUppercase()) 
      return true; 
    } 

    boolean allow_STerm = true, return_value = true; 

    // 8.0 ATerm Close* Sp* × [^ OLetter Upper Lower Sep CR LF STerm ATerm]* Lower 
    Range range = range2; 
    Code code = code2; 
    while (!code.isOLetter() && !code.isUppercase() && !code.isLowercase() && !code.isSep() && !code.isCR() && !code.isLF() && !code.isSTerm() && !code.isATerm()) { 
     if (!(range = text.nextCode(range)).isValid()) 
      break; 
     code = text.code(range); 
    } 
    range = range1; 
    if (code.isLowercase()) { 
     code = code1; 
     allow_STerm = true; 
     goto Sp_Close_ATerm; 
    } 
    code = code1; 

    // 8.1 (STerm | ATerm) Close* Sp* × (SContinue | STerm | ATerm) 
    if (code2.isSContinue() || code2.isSTerm() || code2.isATerm()) 
     goto Sp_Close_ATerm; 

    // 9.0 (STerm | ATerm) Close* × (Close | Sp | Sep | CR | LF) 
    if (code2.isClose()) 
     goto Close_ATerm; 

    // 10.0 (STerm | ATerm) Close* Sp* × (Sp | Sep | CR | LF) 
    if (code2.isSp() || code2.isSep() || code2.isCR() || code2.isLF()) 
     goto Sp_Close_ATerm; 

    // 11.0 (STerm | ATerm) Close* Sp* (Sep | CR | LF)? ÷ 
    return_value = false; 

    // allow Sep, CR, or LF zero or one times 
    for (int iteration = 1; iteration != 0; iteration--) { 
     if (!code.isSep() && !code.isCR() && !code.isLF()) goto Sp_Close_ATerm; 
     if (!(range = text.previousCode(range)).isValid()) goto Sp_Close_ATerm; 
     code = text.code(range); 
    } 

Sp_Close_ATerm: 
    // allow zero or more Sp 
    while (code.isSp() && (range = text.previousCode(range)).isValid()) 
     code = text.code(range); 

Close_ATerm: 
    // allow zero or more Close 
    while (code.isClose() && (range = text.previousCode(range)).isValid()) 
     code = text.code(range); 

    // require STerm or ATerm 
    if (code.isATerm() || (allow_STerm && code.isSTerm())) 
     return return_value; 

    // 12.0  × Any 
    return true; 
} 

Sau đó, bạn có thể lặp qua các câu như vậy:

// pass in a range of (0, 0) to get the range of the first sentence 
// returns a range with a length of 0 if there are no more sentences 
Range nextSentence(Text text, Range range) { 
try_again: 
    range = text.nextCode(new Range(range.start + range.length, 0)); 
    if (!range.isValid()) 
     return range; 
    Range next = text.nextCode(range); 
    long start = range.start; 
    while (next.isValid()) && text.continueSentence(range, next)) 
     next = text.nextCode(range = next); 
    range = new Range(start, range.start + range.length - start); 

    Range range2 = text.trimRange(range); 
    if (!range2.isValid()) 
     goto try_again; 

    return range2; 
} 

đâu:

  • Phạm vi được định nghĩa là một loạt từ> = bắt đầu và < bắt đầu + length
  • text.trimRange loại bỏ các ký tự khoảng trắng (tùy chọn)
  • tất cả các Code.is [Loại] chức năng được tra cứu vào Unicode character database. Ví dụ: bạn sẽ thấy trong một số tệp đó một số điểm mã được định nghĩa là "CR", "Sep", "StartOfText", v.v.
  • Text.code (phạm vi) giải mã điểm mã trong văn bản ở phạm vi. khởi đầu. Chiều dài không được sử dụng.
  • Text.nextCode và Text.previousCode trả về phạm vi của điểm tiếp theo hoặc trước đó trong chuỗi, dựa trên phạm vi của điểm mã hiện tại. Nếu không có điểm mã theo hướng đó, nó sẽ trả về một phạm vi không hợp lệ, mà là một phạm vi với chiều dài 0.

Các tiêu chuẩn cũng xác định cách để lặp qua words, lines, và characters.

Các vấn đề liên quan