【问题标题】:How to calculate syllables in text with regex and Java如何使用正则表达式和 Java 计算文本中的音节
【发布时间】:2016-01-30 05:37:45
【问题描述】:

我的文本为String,需要计算每个单词中的音节数。我试图将所有文本拆分为单词数组,然后分别处理每个单词。我为此使用了正则表达式。但是音节的模式不能正常工作。请建议如何更改它以计算正确的音节数。我的初始代码。

public int getNumSyllables()
{
    String[] words = getText().toLowerCase().split("[a-zA-Z]+");
    int count=0;
    List <String> tokens = new ArrayList<String>();
    for(String word: words){
            tokens = Arrays.asList(word.split("[bcdfghjklmnpqrstvwxyz]*[aeiou]+[bcdfghjklmnpqrstvwxyz]*"));
            count+= tokens.size();

            }
    return count;
}

【问题讨论】:

  • 确定音节比您想象的要困难得多。尝试将 OP 的规则应用于“重新发明”或“细微差别”。而且您忽略了“y”,因此您的规则甚至不适用于“真的”这样的简单词。但“y”并不总是元音。有时(很少)“w”是元音,如“cwm”
  • 而不是 [bcdfghjklmnpqrstvwxyz] 你可以简单地写 [^aeiou]

标签: java arrays regex string


【解决方案1】:

你的线路

String[] words = getText().toLowerCase().split("[a-zA-Z]+");

正在拆分 ON 单词,并且只返回单词之间的空格!您想在单词之间的空格上进行拆分,如下所示:

String[] words = getText().toLowerCase().split("\\s+");

【讨论】:

  • 如果唯一的分隔符是空格,则此方法有效。如果您想忽略所有非单词字符,例如括号、引号等,我会推荐 String[] words = getText().toLowerCase().split("(\\W|_)+") 这个这样,Fitness-Center 就变成了“Fitness”和“Center”,有助于确保跨连字词解析音节的准确性。它还删除了所有标点符号。它不认为数字无效,因此将返回数字,以便根据解析器的要求对其进行处理
【解决方案2】:

这会在一个单词中为您提供多个音节元音:

public int getNumVowels(String word) {

    String regexp = "[bcdfghjklmnpqrstvwxz]*[aeiouy]+[bcdfghjklmnpqrstvwxz]*";
    Pattern p = Pattern.compile(regexp);
    Matcher m = p.matcher(word.toLowerCase());

    int count = 0;

    while (m.find()) {
        count++;
    }
    return count;
}

您可以在字符串数组中的每个单词上调用它:

    String[] words = getText().split("\\s+");
    for (String word : words ) {
      System.out.println("Word: " + word + ", vowels: " + getNumVowels(word));
    }

更新: 正如 freerunner 所指出的,计算音节的数量比仅仅计算元音更复杂。需要考虑诸如 ouuioo、最后的无声 e 之类的组合以及其他可能的组合.由于我不是以英语为母语的人,我不确定正确的算法是什么。

【讨论】:

  • 这是一个很好的解决方案,但“y”也被视为一个音节。所以正则表达式应该是: String regexp = "[bcdfghjklmnpqrstvwxz]*[aeiouy]+[bcdfghjklmnpqrstvwxz]*";
  • 此外,音节还有一个条件,即如果字母'e'在单词的末尾(例如那里)并且单词中还有其他元音,则不计入一个音节。以上代码不满足条件。
【解决方案3】:

您使用的方法拆分不正确。此方法接收分隔符。需要这样写:

String[] words = getText().toLowerCase().split(" ");

但是如果要数音节数,数元音数就足够了:

String input = "text";
Set<Character> vowel = new HashSet<>();
vowel.add('a');
vowel.add('e');
vowel.add('i');
vowel.add('o');
vowel.add('u');

int count = 0;
for (char c : input.toLowerCase().toCharArray()) {
    if (vowel.contains(c)){
        count++;
    }
}

System.out.println("count = " + count);

【讨论】:

  • 您是否考虑到您的方法无法正确计算“Sea”等单词的音节,因为您只是在计算元音! Sea 有一个音节,但你的方法应该返回 2。
  • 是的,你是对的。我想,对于英语和俄语来说,音节数等于元音数。我错了。但是其他正则表达式也错误,因为它们对“帐户”之类的词返回错误的答案。关于这一点,我认为在这个任务中使用正则表达式是个坏主意
  • RegExp,我认为,比这个例子更有效,因为这个例子依赖于 char[1] 的哈希集,它将 char 转换为哈希码,然后将哈希码存储在哈希表中。然后 contains() 方法必须找到具有所述哈希码的所有已注册条目,然后对于每个哈希码,它对键进行相等性检查以确定它是否正确匹配,然后返回布尔值 true/false。我很难确定这是否比 RegExp 解决方案更快或更有效。它当然不再准确。但是,这是尝试解决问题的良好开端。
【解决方案4】:

利用user5500105的概念,我开发了以下方法来计算一个单词的音节数。规则是:

  • 连续元音计为 1 个音节。例如。 "ae" "ou" 是 1 个音节

  • Y被认为是元音

  • 如果 e 是唯一的元音,则末尾的 e 被计为音节:例如:“the”是一个音节,因为末尾的“e”是唯一的元音,而“there”也是 1 个音节,因为“e”是在末尾,单词中还有另一个元音。

     public int countSyllables(String word) {
         ArrayList<String> tokens = new ArrayList<String>();
         String regexp = "[bcdfghjklmnpqrstvwxz]*[aeiouy]+[bcdfghjklmnpqrstvwxz]*";
         Pattern p = Pattern.compile(regexp);
         Matcher m = p.matcher(word.toLowerCase());
    
    
         while (m.find()) {
             tokens.add(m.group());
         }
    
     //check if e is at last and e is not the only vowel or not
         if( tokens.size() > 1 && tokens.get(tokens.size()-1).equals("e")  )
             return tokens.size()-1; // e is at last and not the only vowel so total syllable -1 
         return tokens.size(); 
     }
    

【讨论】:

    【解决方案5】:

    这个问题来自 UCSD 的 Java 课程,对吗?

    我认为您应该为这个问题提供足够的信息,以免让想要提供帮助的人感到困惑。在这里,我有自己的解决方案,已经通过本地程序的测试用例以及 UCSD 的 OJ 进行了测试。

    您错过了有关此问题中音节定义的一些重要信息。 其实我觉得这个问题的关键在于e应该怎么处理。比如假设有te的组合。而如果你把te放在一个单词的中间,当然应该算作一个音节;但是如果在词尾,e 在英文中应该被认为是silent e,所以它不应该被认为是一个音节。

    就是这样。我想用一些伪代码写下我的想法:

      if(last character is e) {
            if(it is silent e at the end of this word) {
               remove the  silent e;
               count the rest part as regular;
            } else {
               count++;
      } else {
            count it as regular;
      }
    }
    

    您可能会发现我不只是使用正则表达式来处理这个问题。其实我想过:这个问题真的可以只用正则表达式来完成吗?我的回答是:不,我不这么认为。至少现在,以 UCSD 为我们提供的知识,要做到这一点太难了。正则表达式是一个强大的工具,它可以非常快速地映射所需的字符。但是正则表达式缺少一些功能。再以te 为例,正则表达式面对teate 之类的词时,将无法三思而后行(这个词我只是举例)。如果我们的正则表达式模式将第一个 te 计为音节,那么为什么最后一个 te 不呢?

    同时,UCSD其实已经在作业纸上谈过了:

    如果您发现自己在做心理操来想出一个正则表达式来直接计算音节,这通常表明有一个更简单的解决方案(提示:考虑一个字符循环——请参阅下面的下一个提示)。仅仅因为一段代码(例如正则表达式)较短并不意味着它总是更好。

    这里的提示是,你应该把这个问题和一些循环一起考虑,结合正则表达式。

    好的,我现在终于应该展示我的代码了:

    protected int countSyllables(String word)
    {
        // TODO: Implement this method so that you can call it from the 
        // getNumSyllables method in BasicDocument (module 1) and 
        // EfficientDocument (module 2).
        int count = 0;
        word = word.toLowerCase();
    
        if (word.charAt(word.length()-1) == 'e') {
            if (silente(word)){
                String newword = word.substring(0, word.length()-1);
                count = count + countit(newword);
            } else {
                count++;
            }
        } else {
            count = count + countit(word);
        }
        return count;
    }
    
    private int countit(String word) {
        int count = 0;
        Pattern splitter = Pattern.compile("[^aeiouy]*[aeiouy]+");
        Matcher m = splitter.matcher(word);
    
        while (m.find()) {
            count++;
        }
        return count;
    }
    
    private boolean silente(String word) {
        word = word.substring(0, word.length()-1);
    
        Pattern yup = Pattern.compile("[aeiouy]");
        Matcher m = yup.matcher(word);
    
        if (m.find()) {
            return true;
        } else
            return false;
    }
    

    你可能会发现除了给定的方法countSyllables之外,我还创建了两个额外的方法countitsilentecountit 用于计算单词中的音节,silente 试图找出以无声e 结尾的单词。并且还应该注意not silent e的定义。例如,the 应视为not silent e,而ate 应视为silent e

    这是我的代码已经通过测试的状态,来自本地测试用例和来自 UCSD 的 OJ:

    来自OJ的测试结果:

    P.S: 直接使用 [^aeiouy] 之类的应该没问题,因为在我们调用这个方法之前会先解析单词。还需要更改为小写字母,这样可以节省大量处理大写字母的工作。我们想要的只是音节的数量。 说到数字,一个优雅的方法是将count定义为静态,这样私有方法可以直接在里面使用count++。但现在好了。

    如果你仍然没有得到这个问题的方法,请随时与我联系:)

    【讨论】:

    • 谢谢安东尼,详细的解释
    • 我不推荐使用 count 作为静态的,因为这使得它不再是线程安全的。如果两个线程试图对不同的文件进行计数,则计数将由两者共享,并导致数据损坏。相反,只需将其作为类级别的实例变量,或完全避免使用全局变量,以确保可伸缩性的任何变化。只值我的 2 美分
    • @Armand 感谢您的评论,我认为您的观点非常有道理。两年前发布这个答案时,我是一名 Java 初学者。如果我有时间,我会看一下代码并尝试使它变得更好。
    【解决方案6】:

    你可以这样做:

    public int getNumSyllables()
    {
        return getSyllables(getTokens("[a-zA-Z]+"));
    }
    
    protected List<String> getWordTokens(String word,String pattern)
    {
        ArrayList<String> tokens = new ArrayList<String>();
        Pattern tokSplitter = Pattern.compile(pattern);
        Matcher m = tokSplitter.matcher(word);
    
        while (m.find()) {
            tokens.add(m.group());
        }
    
        return tokens;
    }
    
    private int getSyllables(List<String> tokens)
    {
        int count=0;
    
        for(String word : tokens)
        if(word.toLowerCase().endsWith("e") && getWordTokens(word.toLowerCase().substring(0, word.length()-1), "[aeiouy]+").size() > 0)
            count+=getWordTokens(word.toLowerCase().substring(0, word.length()-1), "[aeiouy]+").size();
        else
            count+=getWordTokens(word.toLowerCase(), "[aeiouy]+").size();
    
        return count;
    }
    

    【讨论】:

      【解决方案7】:

      这就是我的做法。这是我能想出的最简单的算法。

         public static int syllables(String s) {
            final Pattern p = Pattern.compile("([ayeiou]+)");
            final String lowerCase = s.toLowerCase();
            final Matcher m = p.matcher(lowerCase);
            int count = 0;
            while (m.find())
               count++;
      
            if (lowerCase.endsWith("e"))
               count--;
      
            return count < 0 ? 1 : count;
         }
      

      我将它与 soundex 函数结合使用来确定单词是否听起来相似。音节计数提高了我的 soundex 函数的准确性。

      注意:这仅用于计算单词中的音节。我假设您可以使用 java.util.StringTokenizer 之类的方式解析输入的单词。

      【讨论】:

        【解决方案8】:

        我分别计算the,然后根据以e结尾的单词拆分文本。
        然后数音节,这是我的实现:

        int syllables = 0;
            word = word.toLowerCase();
            if(word.contains("the ")){
                syllables ++;
            }
            String[] split = word.split("e!$|e[?]$|e,|e |e[),]|e$");
        
            ArrayList<String> tokens = new ArrayList<String>();
            Pattern tokSplitter = Pattern.compile("[aeiouy]+");
        
            for (int i = 0; i < split.length; i++) {
                String s = split[i];
                Matcher m = tokSplitter.matcher(s);
        
                while (m.find()) {
                    tokens.add(m.group());
                }
            }
        
            syllables += tokens.size();
        

        我已经测试过所有测试用例都通过了。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-23
          • 1970-01-01
          • 2018-03-08
          • 2020-07-16
          • 2018-03-16
          • 1970-01-01
          相关资源
          最近更新 更多