<NME> WordSources.java
<BEF> /**
 * 
 * APDPlat - Application Product Development Platform
 * Copyright (c) 2013, 杨尚川, yang-shangchuan@qq.com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package org.apdplat.superword.tools;

import org.apache.commons.lang.StringUtils;
import org.apdplat.superword.model.Word;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 从多个文本文件中读取单词
 * 一行一个单词，单词和其他信息之间用空白字符隔开
 * @author 杨尚川
 */
public class WordSources {
    private WordSources(){}
    private static final Logger LOGGER = LoggerFactory.getLogger(WordSources.class);
    private static final Map<String, Set<Word>> CACHE = new ConcurrentHashMap<>();
    public static List<String> getLevels(String word){
        Word w = new Word(word, "");
        List<String> levels = new ArrayList<>();
        if(get("/word_primary_school.txt").contains(w)){
            levels.add("PrimarySchool");
        }
        if(get("/word_junior_school.txt").contains(w)){
            levels.add("JuniorSchool");
        }
        if(get("/word_senior_school.txt").contains(w)){
            levels.add("SeniorSchool");
        }
        if(get("/word_university.txt").contains(w)){
            levels.add("University");
        }
        if(get("/word_new_conception.txt").contains(w)){
            levels.add("NewConception");
        }
        if(get("/word_ADULT.txt").contains(w)){
            levels.add("ADULT");
                List<String> words = Files.readAllLines(Paths.get(url.toURI()));
                Set<Word> wordSet = words.parallelStream()
                                         .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))
                                         .map(line -> new Word(line.split("\\s+")[index].replaceAll("\\s+", ""), null))
                                         .filter(word -> StringUtils.isAlphanumeric(word.getWord()))
                                         .collect(Collectors.toSet());
                set.addAll(wordSet);
        if(get("/word_TEM4.txt").contains(w)){
            levels.add("TEM4");
        }
        if(get("/word_TEM8.txt").contains(w)){
            levels.add("TEM8");
        return set;
    }
    public static void main(String[] args) {
        get("/words.txt", "/words_extra.txt")
                .stream()
                .sorted()
                .forEach(word -> System.out.println(word.getWord()));
        if(get("/word_GRE.txt").contains(w)){
            levels.add("GRE");
        }
        if(get("/word_SAT.txt").contains(w)){
            levels.add("SAT");
        }
        if(get("/word_BEC.txt").contains(w)){
            levels.add("BEC");
        }
        if(get("/word_MBA.txt").contains(w)){
            levels.add("MBA");
        }
        if(get("/word_IELTS.txt").contains(w)){
            levels.add("IELTS");
        }
        if(get("/word_TOEFL.txt").contains(w)){
            levels.add("TOEFL");
        }
        if(get("/word_TOEIC.txt").contains(w)){
            levels.add("TOEIC");
        }
        if(get("/word_KY.txt").contains(w)){
            levels.add("KY");
        }
        return levels;
    }
    /**
     * 考纲词汇
     * @return
     */
    public static Set<Word> getSyllabusVocabulary(){
        return get("/word_primary_school.txt",
                "/word_junior_school.txt",
                "/word_senior_school.txt",
                "/word_university.txt",
                "/word_new_conception.txt",
                "/word_ADULT.txt",
                "/word_CET4.txt",
                "/word_CET6.txt",
                "/word_TEM4.txt",
                "/word_TEM8.txt",
                "/word_CATTI.txt",
                "/word_GMAT.txt",
                "/word_GRE.txt",
                "/word_SAT.txt",
                "/word_BEC.txt",
                "/word_MBA.txt",
                "/word_IELTS.txt",
                "/word_TOEFL.txt",
                "/word_TOEIC.txt",
                "/word_KY.txt");
    }
    public static Set<Word> getAll(){
        Set<Word> data = get("/words.txt", "/word_computer.txt");
        data.addAll(getSyllabusVocabulary());
        return data;
    }
    /**
     * 
     * 一行一个单词，单词和其他信息之间用空白字符隔开
     * 默认 index 为1
     * @param files 单词文件类路径，以/开头
     * @return 不重复的单词集合
     */
    public static Set<Word> get(String... files){
        return get(1, files);
    }

    public static Map<Word, AtomicInteger> convert(Map<String, AtomicInteger> words){
        Map<Word, AtomicInteger> result = new HashMap<>();
        words.keySet().forEach(w -> result.put(new Word(w, ""), words.get(w)));
        return result;
    }

    public static boolean isEnglish(String string){
        for(char c : string.toLowerCase().toCharArray()){
            if(c<'a' || c>'z'){
                return false;
            }
        }
        return true;
    }

    /**
     * 求交集
     * @param first
     * @param second
     * @return
     */
    public static Set<Word> intersection(Set<Word> first, Set<Word> second){
        LOGGER.info("求交集词典1："+first.size());
        LOGGER.info("求交集词典2："+second.size());
        Set<Word> result = first
                .stream()
                .filter(w -> second.contains(w))
                .collect(Collectors.toSet());
        LOGGER.info("交集词典："+result.size());
        return result;
    }
    public static Set<Word> minus(Set<Word> minuend, Set<Word> subtrahend){
        LOGGER.info("被减数个数："+minuend.size());
        LOGGER.info("减数个数："+subtrahend.size());
        Set<Word> result = minuend
                .stream()
                .filter(word -> !subtrahend.contains(word))
                .collect(Collectors.toSet());
        LOGGER.info("结果个数：" + result.size());
        return result;
    }
    public static void save(Set<Word> words, String path){
        try {
            path = "src/main/resources" + path;
            LOGGER.info("开始保存词典：" + path);
            AtomicInteger i = new AtomicInteger();
            List<String> list = words
                        .stream()
                        .sorted()
                        .map(word -> i.incrementAndGet() + "\t" + word.getWord())
                        .collect(Collectors.toList());
            Files.write(Paths.get(path), list);
            LOGGER.info("保存成功");
        }catch (Exception e){
            LOGGER.error("保存词典失败", e);
        }
    }
    /**
     * 一行一个单词，单词和其他信息之间用空白字符隔开
     * @param index 单词用空白字符隔开后的索引，从0开始
     * @param files 单词文件类路径，以/开头
     * @return 不重复的单词集合
     */
    public static Set<Word> get(int index, String... files){
        Set<Word> set = new HashSet<>();
        for(String file : files){
            Set<Word> value = CACHE.get(file);
            if(value != null){
                LOGGER.info("cache hit word file: " + file);
                set.addAll(value);
                continue;
            }

            URL url = null;
            if(file.startsWith("/")){
                url = WordSources.class.getResource(file);
            }else{
                try {
                    url = Paths.get(file).toUri().toURL();
                }catch (Exception e){
                    LOGGER.error("构造URL出错", e);
                }
            }
            if(url == null){
                LOGGER.error("解析词典失败："+file);
                continue;
            }
            LOGGER.info("parse word file: " + url);
            List<String> words = getExistWords(url);
            Set<Word> wordSet = words.parallelStream()
                    .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))
                    .filter(line -> line.trim().split("\\s+").length >= index+1)
                    .map(line -> new Word(line.trim().split("\\s+")[index], ""))
                    .filter(word -> StringUtils.isAlphanumeric(word.getWord()))
                    .collect(Collectors.toSet());
            set.addAll(wordSet);
            CACHE.put(file, wordSet);
        }
        LOGGER.info("unique words count: " + set.size());
        return set;
    }
    private static List<String> getExistWords(URL url){
        try {
            return Files.readAllLines(Paths.get(url.toURI()));
        }catch (Exception e){
            return Collections.emptyList();
        }
    }
    public static Set<Word> stem(Set<Word> words){
        return words
                .stream()
                .filter(word -> word.getWord().length() > 3)
                .filter(word -> !isPlural(words, word))
                .collect(Collectors.toSet());
    }
    public static Map<String, String> plural(Set<Word> words){
        Map<String, String> data = new HashMap<>();
        words
                .stream()
                .filter(word -> word.getWord().length() > 3)
                .forEach(word -> {
                    isPlural(words, word, data);
                });
        return data;
    }
    public static boolean isPlural(Set<Word> words, Word word){
        return isPlural(words, word, new HashMap<>());
    }
    public static boolean isPlural(Set<Word> words, Word word, Map<String, String> data){
        String w = word.getWord();
        //1、以辅音字母+y结尾,变y为i再加es
        if (w.endsWith("ies")){
            char c = w.charAt(w.length()-4);
            if(!(isVowel(c))
                    && words.contains(new Word(w.substring(0, w.length()-4)+"y", ""))){
                log(w, "ies");
                data.put(w, "ies");
                return true;
            }
        }
        //2、以ce, se, ze结尾, 加s
        if(w.endsWith("ces")
                || w.endsWith("ses")
                || w.endsWith("zes")){
            if(words.contains(new Word(w.substring(0, w.length()-1), ""))){
                log(w, "s");
                data.put(w, "s");
                return true;
            }
        }
        //3、以s, sh, ch, x结尾, 加es
        if(w.endsWith("ses")
                || w.endsWith("shes")
                || w.endsWith("ches")
                || w.endsWith("xes")){
            if(words.contains(new Word(w.substring(0, w.length()-2), ""))){
                log(w, "es");
                data.put(w, "es");
                return true;
            }
        }
        //4、一般情况，加s
        if(w.endsWith("s")){
            if(words.contains(new Word(w.substring(0, w.length()-1), ""))){
                log(w, "s");
                data.put(w, "s");
                return true;
            }
        }
        return false;
    }
    private static void log(String word, String suffix){
        LOGGER.debug("发现复数："+word+"\t"+suffix);
    }
    public static boolean isVowel(char _char){
        switch (_char){
            case 'a':return true;
            case 'e':return true;
            case 'i':return true;
            case 'o':return true;
            case 'u':return true;
        }
        return false;
    }
    public static void main(String[] args) {
        //AtomicInteger i = new AtomicInteger();
        //stem(getSyllabusVocabulary()).forEach(w -> System.out.println(i.incrementAndGet() + "、" + w.getWord()));
        String html = HtmlFormatter.toHtmlForPluralFormat(plural(getSyllabusVocabulary()));
        System.out.println(html);
    }
}

<MSG> 移除首尾空白字符

<DFF> @@ -65,7 +65,7 @@ public class WordSources {
                 List<String> words = Files.readAllLines(Paths.get(url.toURI()));
                 Set<Word> wordSet = words.parallelStream()
                                          .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))
-                                         .map(line -> new Word(line.split("\\s+")[index].replaceAll("\\s+", ""), null))
+                                         .map(line -> new Word(line.trim().split("\\s+")[index].replaceAll("\\s+", ""), null))
                                          .filter(word -> StringUtils.isAlphanumeric(word.getWord()))
                                          .collect(Collectors.toSet());
                 set.addAll(wordSet);
@@ -77,7 +77,7 @@ public class WordSources {
         return set;
     }
     public static void main(String[] args) {
-        get("/words.txt", "/words_extra.txt")
+        get("/words.txt", "/words_extra.txt", "/words_gre.txt")
                 .stream()
                 .sorted()
                 .forEach(word -> System.out.println(word.getWord()));

{'commit': 'diff --git a/src/main/java/org/apdplat/superword/tools/WordSources.java b/src/main/java/org/apdplat/superword/tools/WordSources.java\nindex d685d33..fc3c743 100644\n--- a/src/main/java/org/apdplat/superword/tools/WordSources.java\n+++ b/src/main/java/org/apdplat/superword/tools/WordSources.java\n@@ -65,7 +65,7 @@ public class WordSources {\n                 List<String> words = Files.readAllLines(Paths.get(url.toURI()));\n                 Set<Word> wordSet = words.parallelStream()\n                                          .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))\n-                                         .map(line -> new Word(line.split("\\\\s+")[index].replaceAll("\\\\s+", ""), null))\n+                                         .map(line -> new Word(line.trim().split("\\\\s+")[index].replaceAll("\\\\s+", ""), null))\n                                          .filter(word -> StringUtils.isAlphanumeric(word.getWord()))\n                                          .collect(Collectors.toSet());\n                 set.addAll(wordSet);\n@@ -77,7 +77,7 @@ public class WordSources {\n         return set;\n     }\n     public static void main(String[] args) {\n-        get("/words.txt", "/words_extra.txt")\n+        get("/words.txt", "/words_extra.txt", "/words_gre.txt")\n                 .stream()\n                 .sorted()\n                 .forEach(word -> System.out.println(word.getWord()));\n', 'commit_subject': '移除首尾空白字符', 'repo_name': 'ysc/superword', 'committer': {'name': 'ysc', 'email': '8b37f6ec54bd5377666ecc39fb98741165624e5f@qq.com', 'time_sec': '1426170192', 'tz_offset': '480', 'date': {'seconds': '1426170192'}}, 'commit_tree': 'd4c8930de6c10fd915dd0ca9599f7d01fa12fcf5', 'commit_message': '移除首尾空白字符\n', 'language_name': 'JavaScript', 'license': 'apache-2.0', 'hunks': '@@ -65,7 +65,7 @@ public class WordSources {\n                 List<String> words = Files.readAllLines(Paths.get(url.toURI()));\n                 Set<Word> wordSet = words.parallelStream()\n                                          .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))\n-                                         .map(line -> new Word(line.split("\\\\s+")[index].replaceAll("\\\\s+", ""), null))\n+                                         .map(line -> new Word(line.trim().split("\\\\s+")[index].replaceAll("\\\\s+", ""), null))\n                                          .filter(word -> StringUtils.isAlphanumeric(word.getWord()))\n                                          .collect(Collectors.toSet());\n                 set.addAll(wordSet);\n@@ -77,7 +77,7 @@ public class WordSources {\n         return set;\n     }\n     public static void main(String[] args) {\n-        get("/words.txt", "/words_extra.txt")\n+        get("/words.txt", "/words_extra.txt", "/words_gre.txt")\n                 .stream()\n                 .sorted()\n                 .forEach(word -> System.out.println(word.getWord()));\n', 'addition_count': 2, 'deletion_count': 2, 'src_file': 'a/src/main/java/org/apdplat/superword/tools/WordSources.java', 'tgt_file': 'b/src/main/java/org/apdplat/superword/tools/WordSources.java', 'file_extension': '.java', 'before_file': '/**\n * \n * APDPlat - Application Product Development Platform\n * Copyright (c) 2013, 杨尚川, yang-shangchuan@qq.com\n * \n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n * \n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n * \n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n * \n */\n\npackage org.apdplat.superword.tools;\n\nimport org.apache.commons.lang.StringUtils;\nimport org.apdplat.superword.model.Word;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.net.URL;\nimport java.nio.file.Files;\nimport java.nio.file.Paths;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.stream.Collectors;\n\n/**\n * 从多个文本文件中读取单词\n * 一行一个单词，单词和其他信息之间用空白字符隔开\n * @author 杨尚川\n */\npublic class WordSources {\n    private WordSources(){}\n    private static final Logger LOGGER = LoggerFactory.getLogger(WordSources.class);\n    private static final Map<String, Set<Word>> CACHE = new ConcurrentHashMap<>();\n    public static List<String> getLevels(String word){\n        Word w = new Word(word, "");\n        List<String> levels = new ArrayList<>();\n        if(get("/word_primary_school.txt").contains(w)){\n            levels.add("PrimarySchool");\n        }\n        if(get("/word_junior_school.txt").contains(w)){\n            levels.add("JuniorSchool");\n        }\n        if(get("/word_senior_school.txt").contains(w)){\n            levels.add("SeniorSchool");\n        }\n        if(get("/word_university.txt").contains(w)){\n            levels.add("University");\n        }\n        if(get("/word_new_conception.txt").contains(w)){\n            levels.add("NewConception");\n        }\n        if(get("/word_ADULT.txt").contains(w)){\n            levels.add("ADULT");\n                List<String> words = Files.readAllLines(Paths.get(url.toURI()));\n                Set<Word> wordSet = words.parallelStream()\n                                         .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))\n                                         .map(line -> new Word(line.split("\\\\s+")[index].replaceAll("\\\\s+", ""), null))\n                                         .filter(word -> StringUtils.isAlphanumeric(word.getWord()))\n                                         .collect(Collectors.toSet());\n                set.addAll(wordSet);\n        if(get("/word_TEM4.txt").contains(w)){\n            levels.add("TEM4");\n        }\n        if(get("/word_TEM8.txt").contains(w)){\n            levels.add("TEM8");\n        return set;\n    }\n    public static void main(String[] args) {\n        get("/words.txt", "/words_extra.txt")\n                .stream()\n                .sorted()\n                .forEach(word -> System.out.println(word.getWord()));\n        if(get("/word_GRE.txt").contains(w)){\n            levels.add("GRE");\n        }\n        if(get("/word_SAT.txt").contains(w)){\n            levels.add("SAT");\n        }\n        if(get("/word_BEC.txt").contains(w)){\n            levels.add("BEC");\n        }\n        if(get("/word_MBA.txt").contains(w)){\n            levels.add("MBA");\n        }\n        if(get("/word_IELTS.txt").contains(w)){\n            levels.add("IELTS");\n        }\n        if(get("/word_TOEFL.txt").contains(w)){\n            levels.add("TOEFL");\n        }\n        if(get("/word_TOEIC.txt").contains(w)){\n            levels.add("TOEIC");\n        }\n        if(get("/word_KY.txt").contains(w)){\n            levels.add("KY");\n        }\n        return levels;\n    }\n    /**\n     * 考纲词汇\n     * @return\n     */\n    public static Set<Word> getSyllabusVocabulary(){\n        return get("/word_primary_school.txt",\n                "/word_junior_school.txt",\n                "/word_senior_school.txt",\n                "/word_university.txt",\n                "/word_new_conception.txt",\n                "/word_ADULT.txt",\n                "/word_CET4.txt",\n                "/word_CET6.txt",\n                "/word_TEM4.txt",\n                "/word_TEM8.txt",\n                "/word_CATTI.txt",\n                "/word_GMAT.txt",\n                "/word_GRE.txt",\n                "/word_SAT.txt",\n                "/word_BEC.txt",\n                "/word_MBA.txt",\n                "/word_IELTS.txt",\n                "/word_TOEFL.txt",\n                "/word_TOEIC.txt",\n                "/word_KY.txt");\n    }\n    public static Set<Word> getAll(){\n        Set<Word> data = get("/words.txt", "/word_computer.txt");\n        data.addAll(getSyllabusVocabulary());\n        return data;\n    }\n    /**\n     * \n     * 一行一个单词，单词和其他信息之间用空白字符隔开\n     * 默认 index 为1\n     * @param files 单词文件类路径，以/开头\n     * @return 不重复的单词集合\n     */\n    public static Set<Word> get(String... files){\n        return get(1, files);\n    }\n\n    public static Map<Word, AtomicInteger> convert(Map<String, AtomicInteger> words){\n        Map<Word, AtomicInteger> result = new HashMap<>();\n        words.keySet().forEach(w -> result.put(new Word(w, ""), words.get(w)));\n        return result;\n    }\n\n    public static boolean isEnglish(String string){\n        for(char c : string.toLowerCase().toCharArray()){\n            if(c<\'a\' || c>\'z\'){\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * 求交集\n     * @param first\n     * @param second\n     * @return\n     */\n    public static Set<Word> intersection(Set<Word> first, Set<Word> second){\n        LOGGER.info("求交集词典1："+first.size());\n        LOGGER.info("求交集词典2："+second.size());\n        Set<Word> result = first\n                .stream()\n                .filter(w -> second.contains(w))\n                .collect(Collectors.toSet());\n        LOGGER.info("交集词典："+result.size());\n        return result;\n    }\n    public static Set<Word> minus(Set<Word> minuend, Set<Word> subtrahend){\n        LOGGER.info("被减数个数："+minuend.size());\n        LOGGER.info("减数个数："+subtrahend.size());\n        Set<Word> result = minuend\n                .stream()\n                .filter(word -> !subtrahend.contains(word))\n                .collect(Collectors.toSet());\n        LOGGER.info("结果个数：" + result.size());\n        return result;\n    }\n    public static void save(Set<Word> words, String path){\n        try {\n            path = "src/main/resources" + path;\n            LOGGER.info("开始保存词典：" + path);\n            AtomicInteger i = new AtomicInteger();\n            List<String> list = words\n                        .stream()\n                        .sorted()\n                        .map(word -> i.incrementAndGet() + "\\t" + word.getWord())\n                        .collect(Collectors.toList());\n            Files.write(Paths.get(path), list);\n            LOGGER.info("保存成功");\n        }catch (Exception e){\n            LOGGER.error("保存词典失败", e);\n        }\n    }\n    /**\n     * 一行一个单词，单词和其他信息之间用空白字符隔开\n     * @param index 单词用空白字符隔开后的索引，从0开始\n     * @param files 单词文件类路径，以/开头\n     * @return 不重复的单词集合\n     */\n    public static Set<Word> get(int index, String... files){\n        Set<Word> set = new HashSet<>();\n        for(String file : files){\n            Set<Word> value = CACHE.get(file);\n            if(value != null){\n                LOGGER.info("cache hit word file: " + file);\n                set.addAll(value);\n                continue;\n            }\n\n            URL url = null;\n            if(file.startsWith("/")){\n                url = WordSources.class.getResource(file);\n            }else{\n                try {\n                    url = Paths.get(file).toUri().toURL();\n                }catch (Exception e){\n                    LOGGER.error("构造URL出错", e);\n                }\n            }\n            if(url == null){\n                LOGGER.error("解析词典失败："+file);\n                continue;\n            }\n            LOGGER.info("parse word file: " + url);\n            List<String> words = getExistWords(url);\n            Set<Word> wordSet = words.parallelStream()\n                    .filter(line -> !line.trim().startsWith("#") && !"".equals(line.trim()))\n                    .filter(line -> line.trim().split("\\\\s+").length >= index+1)\n                    .map(line -> new Word(line.trim().split("\\\\s+")[index], ""))\n                    .filter(word -> StringUtils.isAlphanumeric(word.getWord()))\n                    .collect(Collectors.toSet());\n            set.addAll(wordSet);\n            CACHE.put(file, wordSet);\n        }\n        LOGGER.info("unique words count: " + set.size());\n        return set;\n    }\n    private static List<String> getExistWords(URL url){\n        try {\n            return Files.readAllLines(Paths.get(url.toURI()));\n        }catch (Exception e){\n            return Collections.emptyList();\n        }\n    }\n    public static Set<Word> stem(Set<Word> words){\n        return words\n                .stream()\n                .filter(word -> word.getWord().length() > 3)\n                .filter(word -> !isPlural(words, word))\n                .collect(Collectors.toSet());\n    }\n    public static Map<String, String> plural(Set<Word> words){\n        Map<String, String> data = new HashMap<>();\n        words\n                .stream()\n                .filter(word -> word.getWord().length() > 3)\n                .forEach(word -> {\n                    isPlural(words, word, data);\n                });\n        return data;\n    }\n    public static boolean isPlural(Set<Word> words, Word word){\n        return isPlural(words, word, new HashMap<>());\n    }\n    public static boolean isPlural(Set<Word> words, Word word, Map<String, String> data){\n        String w = word.getWord();\n        //1、以辅音字母+y结尾,变y为i再加es\n        if (w.endsWith("ies")){\n            char c = w.charAt(w.length()-4);\n            if(!(isVowel(c))\n                    && words.contains(new Word(w.substring(0, w.length()-4)+"y", ""))){\n                log(w, "ies");\n                data.put(w, "ies");\n                return true;\n            }\n        }\n        //2、以ce, se, ze结尾, 加s\n        if(w.endsWith("ces")\n                || w.endsWith("ses")\n                || w.endsWith("zes")){\n            if(words.contains(new Word(w.substring(0, w.length()-1), ""))){\n                log(w, "s");\n                data.put(w, "s");\n                return true;\n            }\n        }\n        //3、以s, sh, ch, x结尾, 加es\n        if(w.endsWith("ses")\n                || w.endsWith("shes")\n                || w.endsWith("ches")\n                || w.endsWith("xes")){\n            if(words.contains(new Word(w.substring(0, w.length()-2), ""))){\n                log(w, "es");\n                data.put(w, "es");\n                return true;\n            }\n        }\n        //4、一般情况，加s\n        if(w.endsWith("s")){\n            if(words.contains(new Word(w.substring(0, w.length()-1), ""))){\n                log(w, "s");\n                data.put(w, "s");\n                return true;\n            }\n        }\n        return false;\n    }\n    private static void log(String word, String suffix){\n        LOGGER.debug("发现复数："+word+"\\t"+suffix);\n    }\n    public static boolean isVowel(char _char){\n        switch (_char){\n            case \'a\':return true;\n            case \'e\':return true;\n            case \'i\':return true;\n            case \'o\':return true;\n            case \'u\':return true;\n        }\n        return false;\n    }\n    public static void main(String[] args) {\n        //AtomicInteger i = new AtomicInteger();\n        //stem(getSyllabusVocabulary()).forEach(w -> System.out.println(i.incrementAndGet() + "、" + w.getWord()));\n        String html = HtmlFormatter.toHtmlForPluralFormat(plural(getSyllabusVocabulary()));\n        System.out.println(html);\n    }\n}\n', 'message': '移除首尾空白字符\n', 'author_name': 'ysc', 'lang': 'java'}