检测字符串是不是包含多个单词的更好方法

Posted

技术标签:

【中文标题】检测字符串是不是包含多个单词的更好方法【英文标题】:Better way to detect if a string contains multiple words检测字符串是否包含多个单词的更好方法 【发布时间】:2013-09-23 23:15:27 【问题描述】:

我正在尝试创建一个程序,以尽可能快地检测字符串中是否有多个单词,如果是,则执行一个行为。最好,我也希望它能够检测这些单词的顺序,但前提是这可以快速完成。到目前为止,这就是我所做的:

if (input.contains("adsf") && input.contains("qwer")) 
    execute();          

如您所见,对多个单词执行此操作会变得很烦人。这是唯一的方法还是有更好的方法来检测多个子字符串?有什么方法可以检测顺序吗?

【问题讨论】:

【参考方案1】:

我会根据这些词创建一个正则表达式:

Pattern pattern = Pattern.compile("(?=.*adsf)(?=.*qwer)");
if (pattern.matcher(input).find()) 
    execute();

更多详情请看这个答案:https://***.com/a/470602/660143

【讨论】:

这仅处理匹配的第一个单词,并根据我的判断返回 true,如果所有单词都在输入字符串中,则不匹配。 我喜欢在这里使用正则表达式,但是在浏览大量文本时这对我来说非常慢。我发现@Jack 的答案要快得多(在我的用例中)。 同意,正则表达式本身非常昂贵,如果您需要最高速度,则不应使用它们。 如果目标是可维护性(来自问题“对多个单词执行此操作会变得令人厌烦”),则此解决方案无法解决它。每个单词使用 6 个额外字符。【参考方案2】:

编者注:尽管被大力支持和接受,但它的功能与问题中的代码不同。 execute 在第一次匹配时被调用,就像一个逻辑 OR。

你可以使用数组:

String[] matches = new String[] "adsf", "qwer";

bool found = false;
for (String s : matches)

  if (input.contains(s))
  
    execute();
    break;
  

这与您发布的一样有效,但更易于维护。寻找更有效的解决方案听起来像是一种微优化,在被证明是代码的有效瓶颈之前应该忽略它,在任何情况下,如果字符串集很大,该解决方案可能会是一个尝试。

【讨论】:

嗯,我相信这对我的小项目应该很有效。谢谢这么快的回复! 这实际上与相关代码的工作方式相同吗?这个应该更像一个 or 运算符。 对于大词集来匹配一个选项是 aho-corasick 算法试试这个库 -> github.com/robert-bor/aho-corasick 快速性能改进是用 for-i 循环替换 for-each。 Java 中的 for-each 创建一个迭代器对象。对象创建是昂贵的。如果您正在优化代码以在 200 毫秒内执行,那么优化是不值得的。但是,如果您要针对更关键的性能进行优化,那么避免创建对象会有很大的不同。 @ThomasFischer:如果代码对时间要求很高,那么问题就出在算法本身上,更快的方法(例如针对多个字符串优化的 Bayer-Moore)将是一个合适的解决方案。【参考方案3】:

在 Java 8 中你可以这样做

public static boolean containsWords(String input, String[] words) 
    return Arrays.stream(words).allMatch(input::contains);

示例用法:

String input = "hello, world!";
String[] words = "hello", "world";
if (containsWords(input, words)) System.out.println("Match");

【讨论】:

【参考方案4】:

如果您有很多子字符串要查找,那么正则表达式可能不会有太大帮助,因此您最好将子字符串放在一个列表中,然后遍历它们并调用 input.indexOf(substring)在每一个上。这将返回找到子字符串的位置的int 索引。如果您将每个结果(-1 除外,这意味着未找到子字符串)放入 TreeMap(其中 index 是键,子字符串是值),那么您可以通过调用按顺序检索它们keys() 在地图上。

Map<Integer, String> substringIndices = new TreeMap<Integer, String>();
List<String> substrings = new ArrayList<String>();
substrings.add("asdf");
// etc.

for (String substring : substrings) 
  int index = input.indexOf(substring);

  if (index != -1) 
    substringIndices.put(index, substring);
  


for (Integer index : substringIndices.keys()) 
  System.out.println(substringIndices.get(index));

【讨论】:

【参考方案5】:

使用树结构来保存每个代码点的子字符串。这样就不需要

请注意,这只有在针组几乎恒定时才有效。虽然单独添加或删除子字符串并不是低效的,但是每次将大量字符串排列成树结构时进行不同的初始化肯定会减慢它。

StringSearcher:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

class StringSearcher
    private NeedleTree needles = new NeedleTree(-1);
    private boolean caseSensitive;
    private List<Integer> lengths = new ArrayList<>();
    private int maxLength;

    public StringSearcher(List<String> inputs, boolean caseSensitive)
        this.caseSensitive = caseSensitive;
        for(String input : inputs)
            if(!lengths.contains(input.length()))
                lengths.add(input.length());
            
            NeedleTree tree = needles;
            for(int i = 0; i < input.length(); i++)
                tree = tree.child(caseSensitive ? input.codePointat(i) : Character.toLowerCase(input.codePointAt(i)));
            
            tree.markSelfSet();
        
        maxLength = Collections.max(legnths);
    

    public boolean matches(String haystack)
        if(!caseSensitive)
            haystack = haystack.toLowerCase();
        
        for(int i = 0; i < haystack.length(); i++)
            String substring = haystack.substring(i, i + maxLength); // maybe we can even skip this and use from haystack directly?
            NeedleTree tree = needles;
            for(int j = 0; j < substring.maxLength; j++)
                tree = tree.childOrNull(substring.codePointAt(j));
                if(tree == null)
                    break;
                
                if(tree.isSelfSet())
                    return true;
                
            
        
        return false;
    

NeedleTree.java:

import java.util.HashMap;
import java.util.Map;

class NeedleTree
    private int codePoint;
    private boolean selfSet;
    private Map<Integer, NeedleTree> children = new HashMap<>();

    public NeedleTree(int codePoint)
        this.codePoint = codePoint;
    

    public NeedleTree childOrNull(int codePoint)
        return children.get(codePoint);
    

    public NeedleTree child(int codePoint)
        NeedleTree child = children.get(codePoint);
        if(child == null)
            child = children.put(codePoint, new NeedleTree(codePoint));
        
        return child;
    

    public boolean isSelfSet()
        return selfSet;
    

    public void markSelfSet()
        selfSet = true;
    

【讨论】:

【参考方案6】:

这是一个经典的面试和 CS 问题。

Robin Karp 算法通常是人们在采访中首先谈论的内容。基本思想是,在遍历字符串时,将当前字符添加到散列中。如果哈希与您的一个匹配字符串的哈希匹配,则您知道您可能有一个匹配项。这避免了在匹配字符串中来回扫描。 https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm

该面试问题的其他典型主题是考虑使用 trie 结构来加快查找速度。如果您有大量匹配字符串,则必须始终检查大量匹配字符串。 trie 结构更有效地进行检查。 https://en.wikipedia.org/wiki/Trie

其他算法有: - 阿霍-科拉西克https://en.wikipedia.org/wiki/Aho%E2%80%93Corasick_algorithm - Commentz-Walter https://en.wikipedia.org/wiki/Commentz-Walter_algorithm

【讨论】:

【参考方案7】:

我认为更好的方法是这样的,我们可以将多个值添加为一个字符串并通过函数索引验证索引

String s = "123"; 
System.out.println(s.indexOf("1")); // 0
System.out.println(s.indexOf("2")); // 1 
System.out.println(s.indexOf("5")); // -1

【讨论】:

以上是关于检测字符串是不是包含多个单词的更好方法的主要内容,如果未能解决你的问题,请参考以下文章

pandas loc 检查值是不是包含多个单词或字符串中的任何一个

检查字符串是不是包含破折号并在单词中添加双引号

pyspark字符串匹配多个精确单词正则表达式的有效方法

Javascript/jQuery 检查字符串是不是包含一个单词或出现的可选单词之一

ctype_alpha() 是检查字符串是不是仅包含字母的更好方法吗

通过python将一个大字符串拆分为包含'n'个单词的多个子字符串