我有一个大型的句子列表和另一个单词列表。
我的程序循环遍历数组列表,如果句子包含来自另一个的任何单词,则从该数组列表中删除一个元素。
句子数组列表可能非常大,我编写了一个快速而脏的嵌套for循环。虽然这适用于没有多少句子的情况,但是如果它们是,那么完成这项操作所需的时间非常长。
controller
有没有更有效的方法来执行此操作然后嵌套for循环?
答案 0 :(得分:6)
您的代码中存在一些效率低下的问题,但在一天结束时,如果您必须搜索包含单词的句子,那么就无法摆脱循环。
那就是说,有几件事要尝试。
首先,将WORDS
设为HashSet
,contains
方法将比ArrayList
快得多,因为它正在进行哈希查找得到价值。
其次,切换逻辑有点像这样:
Iterator<String> sentenceIterator = SENTENCES.iterator();
sentenceLoop:
while (sentenceIterator.hasNext())
{
String sentence = sentenceIterator.next();
for (String word : sentence.replaceAll("\\p{P}", " ").toLowerCase().split("\\s+"))
{
if (WORDS.contains(word))
{
sentenceIterator.remove();
continue sentenceLoop;
}
}
}
此代码(假设您尝试删除包含某些字词的句子)使用Iterator
并避免使用原始代码中的string
连接和解析逻辑(替换它)使用单个正则表达式)两者都应该更快。
但请记住,与所有表现一样,您需要测试这些变化,看看它们是否会改善这种情况。
答案 1 :(得分:4)
I̶̶w̶o̶u̶l̶d̶̶s̶a̶y̶̶n̶o̶,̶̶b̶u̶t̶你必须改变的是你处理数据删除的方式。这一部分解释了你的问题:
句子数组列表可能非常大(...)。虽然这适用于没有多少句子的情况,但是如果它们是,那么完成这项操作所需的时间非常长。
原因是ArrayList
中的删除时间需要O(N),并且因为你在循环内执行此操作,所以它至少需要O(N ^ 2)。
我建议使用LinkedList
而不是ArrayList
来存储句子,并使用Iterator
而不是您的幼稚List#get
,因为它已及时提供Iterator#remove
LinkedList
的O(1)。
如果您无法将设计更改为LinkedList
,我建议您在新的List
中存储有效的句子,最后将原始List
的内容替换为这个新List
,因此节省了大量时间。
除了这一重大改进之外,您还可以使用Set
来存储要查找的字词,而不是使用其他List
,因为Set
中的查找是O(1)。
答案 2 :(得分:1)
我将从第二个ArrayList创建一组单词:
Set<String> listOfWords = new HashSet<String>();
listOfWords.add("one");
listOfWords.add("two");
然后我将遍历集合和第一个ArrayList并使用Contains:
for (String word : listOfWords) {
for(String sentence : Sentences) {
if (sentence.contains(word)) {
// do something
}
}
}
此外,如果您可以自由使用任何开源jar,请查看:
答案 3 :(得分:1)
首先,你的程序有一个错误:它不会在句子的开头和结尾处计算单词。
您当前的程序的运行时复杂度为O(s * w),其中s是所有句子的长度(字符),w是所有单词的长度,也是字符。
如果words
相对较小(几百项左右),您可以使用正则表达式来大大加快速度:构建这样的模式,并在循环中使用它:
StringBuilder regex = new StringBuilder();
boolean first = true;
// Let's say WORDS={"quick", "brown", "fox"}
regex.append("\\b(?:");
for (String w : WORDS) {
if (!first) {
regex.append('|');
} else {
first = false;
}
regex.append(w);
}
regex.append(")\\b");
// Now regex is "\b(?:quick|brown|fox)\b", i.e. your list of words
// separated by OR signs, enclosed in non-capturing groups
// anchored to word boundaries by '\b's on both sides.
Pattern p = Pattern.compile(regex.toString());
for (int i = 0; i < SENTENCES.size(); i++) {
if (p.matcher(SENTENCES.get(i)).find()) {
// Do something
}
}
由于正则表达式被预编译成更适合快速搜索的结构,因此您的程序将以O(s * max(w))运行,其中s
是所有句子的字符长度,和w是最长单词的长度。鉴于您的集合中的单词数量大约为200或300,这可以使您的运行时间减少一个数量级。
答案 4 :(得分:1)
你能做的就是把所有的单词都放到HashSet中。这允许您非常快速地检查单词是否在集合中。有关文档,请参阅https://docs.oracle.com/javase/8/docs/api/java/util/HashSet.html。
HashSet<String> wordSet = new HashSet();
for (String word : WORDS) {
wordSet.add(word);
}
然后,只需将每个句子分成构成单词的单词,并检查这些单词中是否有任何单词。
for (String sentence : SENTENCES) {
String[] sentenceWords = sentence.split(" "); // You probably want to use a regex here instead of just splitting on a " ", but this is just an example.
for (String word : sentenceWords) {
if (wordSet.contains(word)) {
// The sentence contains one of the special words.
// DO SOMETHING
break;
}
}
}
答案 5 :(得分:0)
如果你有足够的内存,你可以对SENTENCES进行标记并将它们放入Set中。那么它的性能会更好,也比当前的实现更正确。
答案 6 :(得分:0)
好吧,看看你的代码,我会提出两个可以提高每次迭代性能的方法:
" " + WORDS.get(k) + " "
)内连接字符串,因为它是一个非常昂贵的操作,因为+运算符会创建新对象。更好地使用字符串缓冲区/构建器,并在每次迭代后使用stringBuffer.setLength(0);
清除它。除此之外,对于这种情况,我不知道任何其他方法,也许你可以使用正则表达式,如果你可以从你想要删除的那些单词中抽象出一个模式,然后只有一个循环。
希望它有所帮助!
答案 7 :(得分:0)
如果您关注效率,我认为最有效的方法是使用Aho-Corasick's算法。虽然你有两个嵌套循环和一个contains()
方法(我认为最好的句子长度+单词长度时间),Aho-Corasick给你一个句子循环检查包含单词需要句子长度,这是单词长度倍快(+创建有限状态机的预处理时间,相对较小)。
答案 8 :(得分:0)
我将在更多的理论视图中解决这个问题。如果你没有内存限制,你可以尝试模仿计算排序的逻辑
说M1 = sentences.size,M2 =每个句子的单词数,N = word.size
为简单起见,假设所有句子的单词数相同
你当前的方法的复杂性是O(M1.M2.N)
我们可以创建单词的映射 - 句子中的位置。
循环你的句子的arraylist,并将它们变成二维锯齿状的单词阵列。循环遍历新数组,创建一个HashMap,其中key,value = words,单词位置的arraylist(比如长度为X)。
那是O(2M1.M2.X)= O(M1.M2.X)
然后遍历你的单词arraylist,访问你的单词hashmap,循环遍历单词位置列表。删除每一个。那是O(N.X)
假设您需要在字符串的arraylist中给出结果,我们需要另一个循环并连接所有内容。那是O(M1.M2)
总复杂度为O(M1.M2.X)+ O(N.X)+ O(M1.M2)
假设X小于N,你可能会获得更好的性能