优化大型数据集的搜索技术



我目前正在进行一个项目,需要处理一个大约300万行长的.csv文件和大小在10行到1000行之间的不同.xlsx文件。我试图在.xlsx文件和.csv文件中找到不同单元格之间的共性。这样做。我已经读取了.csv文件和.xslx文件,并将它们都存储在ArrayLists中。我有我想做的,但我使用的方法是O(n^3)使用3嵌套的for循环在每个循环之间进行搜索。

//This is our .xlsx file stored in an ArrayList
for(int i = 1; i<finalKnowledgeGraph.size(); i+=3) {
//loop through our knowledgeGraph again
for(int j = 1; j<finalKnowledgeGraph.size(); j+=3) {
//loop through .csv file which is stored in an ArrayList
for(int k=1; k<storeAsserions.size(); k++) {
if(finalKnowledgeGraph.get(i).equals(storeAsserions.get(k)) && finalKnowledgeGraph.get(j+1).equals(storeAsserions.get(k+1))){
System.out.println("Do Something");
} else if(finalKnowledgeGraph.get(i+1).equals(storeAsserions.get(k)) && finalKnowledgeGraph.get(j).equals(storeAsserions.get(k+1))) {
System.out.println("Do something else");
}
}
}
}

目前,在我的实际代码中,我的System.out.println("Do something")只是将每个文件的特定部分写入一个新的.csv文件。

现在,我正在做的是优化问题。显然,如果我在数百万个输入上运行一个3嵌套的for循环,它在我的一生中不会完成运行,所以我想知道我可以用什么方法优化代码。

我的一个朋友建议将文件存储在内存中,这样读/写速度会快几倍。另一位朋友建议将文件存储在哈希表中,而不是ArrayLists中,以帮助加快进程,但由于我基本上是在搜索所述哈希表中的每个元素,我不知道这将如何加快进程。它似乎将把搜索从一个数据结构转移到另一个数据架构。然而,我说过我也会在这里发布这个问题,看看人们是否对我如何优化这个代码有任何提示/建议。感谢

注意:我自己根本不知道优化等,我发现S/O上的其他问题对我在该领域的知识来说太具体了,所以如果这个问题看起来像是重复的,我可能已经看到了你谈论的问题,无法理解的内容

编辑:存储在两个ArrayList中的所有内容都是动词:名词:名词对,我试图比较每个ArrayList之间的名词。由于我不关心动词,所以我从索引1开始搜索。(仅供参考)

一个可能的解决方案是使用数据库,如果有适当的索引,它可以很快完成搜索。假设数据适合内存,您可以更快。

原理

对于等问题

for (X x : xList) {
for (Y y : yList) {
if (x.someAttr() == y.someAttr()) doSomething(x, y);
}
}

您只需根据属性(如)将一个列表划分为多个存储桶

Map<A, List<Y>> yBuckets = new HashMap<>();
yList.forEach(y -> yBuckets.compute(y.someAttr(), (k, v) ->
(v==null ? new ArrayList<>() : v).add(y));

现在,您迭代其他列表,只查看像这样的适当bucket中的元素

for (X x : xList) {
List<Y> smallList = yBucket.get(x.someAttr());
if (smallList != null) {
for (Y y : smallList) {
if (x.someAttr() == y.someAttr()) doSomething(x, y);
}
}
}

这种比较实际上可以忽略不计,因为它总是正确的,但这不是重点。速度从消除到查看equals返回false的情况。

复杂性从二次型降低到线性,再加上对doSomething的调用次数。

您的案例

您的数据结构显然不合适。你把你的三胞胎放在一个列表里,这是错误的。你当然可以以某种方式解决它,但创建一个class Triplet {String verb, noun1, noun2}会让一切变得更简单。对于storeAsserions,看起来您使用的是成对的。它们似乎有重叠,但这可能是一个拼写错误,不管怎样,这都无关紧要。让我们使用Triplets和Pairs。

让我也重命名你的列表,这样代码就更适合这个小窗口:

for (Triplet x : fList) {
for (Triplet y : fList) {
for (Pair z : sList) {
if (x.noun1.equals(z.noun1) && y.noun2.equals(z.noun2)) {
doSomething();
} else if (x.noun2.equals(z.noun1) && y.noun1.equals(z.noun2)) {
doSomethingElse();
}
}
}
}

现在,我们需要在bucket上进行一些循环,这样至少有一个equals测试始终为真,这样我们就可以省去处理不匹配数据的时间。让我们集中讨论的第一个条件

x.noun1.equals(z.noun1) && y.noun2.equals(z.noun2)

我建议使用类似的循环

for (Pair z : sList) {
for (Triplet x : smallListOfTripletsHavingNoun1SameAsZ) {
for (Triplet y : smallListOfTripletsHavingNoun2SameAsZ) {
doSomething();
}
}
}

其中小列表像第一节中那样进行计算。

从不比较不匹配的条目,因此复杂性从三次减少到匹配的数量(=如果要打印代码行,则减少到匹配数量)。

附录——yBuckets

假设xList看起来像

[
{id: 1, someAttr: "a"},
{id: 2, someAttr: "a"},
{id: 3, someAttr: "b"},
]

那么yBuckets应该是

{
"a": [
{id: 1, someAttr: "a"},
{id: 2, someAttr: "a"},
],
:b": [
{id: 3, someAttr: "b"},
],
}

一个简单的方法,如何创建这样的地图是

yList.forEach(y -> yBuckets.compute(y.someAttr(), (k, v) ->
(v==null ? new ArrayList<>() : v).add(y));

明文:

对于来自CCD_ 13的每个CCD_
  • 得到CCD_ 14形式的对应映射条目
  • v为null时,则创建一个新列表
  • 否则使用列表v
  • 在任何情况下,将y添加到其中
  • 并将其存储回Map(除非在第三步中创建了新的List,否则这是不操作的)
  • 最新更新