使用Java中的另一个Arraylist循环遍历ArrayList

时间:2015-07-17 14:45:28

标签: java arrays arraylist

我有一个大型的句子列表和另一个单词列表。

我的程序循环遍历数组列表,如果句子包含来自另一个的任何单词,则从该数组列表中删除一个元素。

句子数组列表可能非常大,我编写了一个快速而脏的嵌套for循环。虽然这适用于没有多少句子的情况,但是如果它们是,那么完成这项操作所需的时间非常长。

controller

有没有更有效的方法来执行此操作然后嵌套for循环?

9 个答案:

答案 0 :(得分:6)

您的代码中存在一些效率低下的问题,但在一天结束时,如果您必须搜索包含单词的句子,那么就无法摆脱循环。

那就是说,有几件事要尝试。

首先,将WORDS设为HashSetcontains方法将比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,请查看:

searching string in another string

答案 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)

好吧,看看你的代码,我会提出两个可以提高每次迭代性能的方法:

  1. 删除“== true”。 contains操作已经返回一个布尔值,因此对于if来说就足够了,将它与true进行比较会为每次不需要的迭代添加一个额外的操作。
  2. 不要在循环(" " + WORDS.get(k) + " ")内连接字符串,因为它是一个非常昂贵的操作,因为+运算符会创建新对象。更好地使用字符串缓冲区/构建器,并在每次迭代后使用stringBuffer.setLength(0);清除它。
  3. 除此之外,对于这种情况,我不知道任何其他方法,也许你可以使用正则表达式,如果你可以从你想要删除的那些单词中抽象出一个模式,然后只有一个循环。

    希望它有所帮助!

答案 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,你可能会获得更好的性能