【问题标题】:Check if a sentence contains a word from a list of word检查句子是否包含单词列表中的单词
【发布时间】:2018-04-21 07:08:40
【问题描述】:

我想检查一个句子是否包含映射到某个类别的单词列表中的单词。所以我有一个带有单词、类别名称和方法 filterCategory 的 KeyValue.java 类来检查它是否包含单词。现在我有 10,000 个关键字为文本映射了不同的类别。但问题是它的速度很慢。您能否建议一些替代方法来加快分类速度。感谢您的帮助。

public class KeyValue {
private String key;
private String value;

public KeyValue(String key, String value) {
    this.key = key;
    this.value= value;
}
public KeyValue() {
}
public String getKey() {
    return key;
}
public void setKey(String key) {
    this.key = key;
}
public String getValue() {
    return value;
}
public void setValue(String value) {
    this.value = value;
}

分类.java

class Classification
{

private static List<KeyValue> keyMap = new ArrayList<KeyValue>();

static{
    getWordMap();
}

public static List<KeyValue> getWordMap()
{           
    if(keyMap.size()==0)
    {
        keyMap.add(new KeyValue("sports","football"));
        keyMap.add(new KeyValue("sports","basketball"));
        keyMap.add(new KeyValue("sports","olympics"));
        keyMap.add(new KeyValue("sports","cricket"));
        keyMap.add(new KeyValue("sports","t20"));
    }
}

public static KeyValue filterCategory(String filteredText)
{               
    KeyValue kv = null;

    for(KeyValue tkv:keyMap)
    {
        String value = tkv.getValue();
        String lc = filteredText.toLowerCase();
        lc = FormatUtil.replaceEnglishSymbolsWithSpace(lc);//remove symbols with space and then normalizes it

        String lastWord="";
        if(lc.contains(" "))
        {
            lastWord = lc.substring(lc.lastIndexOf(" ")+1);

            if(lc.startsWith(value+" ") || lc.contains(" "+value+" ") || value.equals(lastWord))
            {
                kv = new KeyValue(tkv.getKey(), tkv.getValue());
                break;
            }               
        }
        else if(lc.contains(value))
        {
            kv = new KeyValue(tkv.getKey(), tkv.getValue());
            break;              
        }
    }

    if(kv==null)
    {
        return new KeyValue("general","0");
    }
    else 
    {
        kv.setValue("100");
        return kv;
    }
}
}

【问题讨论】:

  • 查看 Guava 的 Multimap,因为它是一个很好的 ADT。
  • 我不确定它是否会以任何方式提高性能。
  • 当我将 lowercase(和 FormatUtil 调用)移到 for 循环前面时,快速测试给了我很大的改进。您不必为每个键都这样做。
  • 您的权利。我搞砸了。
  • 您甚至可以在循环之前检查lastWordcontains(" "),然后以一种或另一种方式循环,而无需一次又一次地检查。想想帮助方法 containsKey(lc)equalWord(lc, lastWord),在其中循环键。

标签: java regex collections text-classification multilabel-classification


【解决方案1】:

根据建议,我发布了我能想到的最快的代码。

基于KeyValue的List修改为简单的HashMap

private static HashMap<String,String> map = new HashMap<String,String>();

感谢您的建议。它现在可以扩展以投入生产。

public static KeyValue filterCategory(String filteredText)
{               
    KeyValue kv = null;
    filteredText = filteredText.toLowerCase();
    filteredText = FormatUtil.replaceEnglishSymbolsWithSpace(filteredText);

    StringTokenizer tokenizer = new StringTokenizer(filteredText);
    while(tokenizer.hasMoreTokens()) {
        String temp = tokenizer.nextToken();
        if(map.containsKey(temp))
        {
            kv = new KeyValue(map.get(temp),"100");
            break;
        }
    }       
    if(kv==null)
    {
        kv= new KeyValue("general","0");
    }
    return kv;
}

【讨论】:

    【解决方案2】:

    您的实现是合理的,但您的 KeyValue 对象使用 Exhaustive or Brute-Force Search 算法,而不是更快的匹配算法,例如 HashingHashMap or Hashtable 对象。

    假设

    • 您有 10,000 个映射词。
    • 您正在尝试将这些单词与英语句子或短语进行匹配,例如“The quick brown fox jumps over the lazy dog

    问题

    如您所写,您的逻辑将执行暴力搜索,尝试为您句子中的每个单词进行可能的 10,000 次匹配。如果您的 KeyValue 对象中不存在句子中的每个单词,则使用上面给出的短语将创建 (10,000) x (9) = 90,000 次最大尝试。

    这个逻辑创建了一个最坏的情况,或Big-OΘ(n) 的性能损失,其中 n 表示列表中的单词数。这称为线性搜索。对此方法的一个懒惰改进是使用排序列表,为您提供更好的Θ(log(n))对数搜索时间。

    修复

    不要执行蛮力搜索,而是使用哈希算法一次对整个单词执行查找;或者,如果您想通过字符移位执行模式匹配,请查看Rabin—Karp Hash Algorithm。在仅匹配整个单词的简化情况下,您的算法会将句子中的单词分解为标记(就像您现在所做的那样),然后使用哈希函数查找值和相关类别的哈希图。

    您的新逻辑将具有 Θ(1) 的 Big-O 性能。这种恒定时间匹配将大大提高您的应用程序的速度。

    伪代码

    // Adapting your KeyValue into a simple <Value, Key> map e.g. <"football", "sports">
    //HashMap<String, String> map = new HashMap<String, String>();
    
    // Adapting your KeyValue into a <Value, Set<Key>> map for multiple 
    //    category keys e.g. <"football", <"sports","sunday","games">>
    HashMap<String, Set<String>> map = new HashMap<String, Set<String>>();
    
    // build the hashmap with your values and categories
    Set<String> categories = new HashSet<String>();
    categories.add("sports");
    categories.add("sunday");
    categories.add("games");
    map.put("football", categories);
    ...
    
    // sanitize your input
    String lc = filteredText.toLowerCase();
    lc = FormatUtil.replaceEnglishSymbolsWithSpace(lc);
    
    // tokenize your sentence
    String[] tokens = lc.split("\\s");
    ...
    
    // search tokens against your hashmap
    for (String token : tokens) {
    
        // search the token against the hashmap
        if (map.containsKey(token)){
            Set<String> cats = map.get(token);
            ...
        } else {
            ...
        }
    }
    

    【讨论】:

    • 这不是 0(1) 。我们仍将循环遍历标记并取决于句子。可能是有很多代币的报纸文章。
    • @akay 将句子中的单词与标记的 HashMap 匹配的行为变为 Θ(1) 而不是 Θ(n)。这是因为哈希函数是一个常数时间查找,与匹配列表中每个令牌直到找到(或未找到)的线性尝试相比。如果我遗漏了什么,您能否进一步解释一下?
    • 我们将 10,000 个单词映射到 100 个类别。要查找一个句子是否包含该关键字,您必须在最坏的情况下检查每个单词与 10,000 个单词。这怎么可能是 0(1) ?如果您可以发布任何比我在下面发布的代码运行得更快的代码,那将有很大帮助。
    【解决方案3】:

    我不明白你为什么不使用 Java 的 util.Map 来解决这个问题,但我建议你重复使用:

    lc = FormatUtil.replaceEnglishSymbolsWithSpace(lc);
    String result= Arrays.stream(lc.split(" ")).filter(s -> s.equals(value)).findFirst().orElse("");
                if(result.length()>0) {
                    kv = tkv;
                }
    

    【讨论】:

    • Op 的匹配条件不同。如果有多个单词,则仅当它们相等时才匹配。否则它已经在 contains 上匹配了。
    猜你喜欢
    • 1970-01-01
    • 2020-07-06
    • 1970-01-01
    • 2021-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-21
    相关资源
    最近更新 更多