【问题标题】:Reduce the complexity of matching the elements of two arrays降低匹配两个数组元素的复杂度
【发布时间】:2020-05-09 14:18:32
【问题描述】:

我编写了一个代码,它从 google 工作表中提取列标题(工作表中的第一行)并将它们与对象数组进行比较。对象数组中的每个对象都有 3 个属性:“问题”、“答案”和“类别”。代码将每列的标题与数组中每个对象的“问题”属性进行比较。

如果它们相似,则应将列的索引作为键添加到某个字典中,并将其值设置为包含该问题的答案和类别的数组。无需过多解释我为什么要这样做,但我简要地构建了这个逻辑,以便能够对申请人对某些问题的回答进行评分(因此将问题的索引与其正确答案及其类别相关联)。这是代码:

 for (i = 0; i<columnHeaders[0].length; i++){
    for (j=0; j<questionsObjects.length; j++){
      //Get the current question and convert it to lower case
      question = questionsObjects[j].question.toString().toLowerCase(); 

      //Get column header, remove any spaces and new lines from it, and convert it to lower case
      columnHeader = columnHeaders[0][i].toString().toLowerCase();


      if (isStringSimilar(columnHeader, question)){

        //Link the column index to its corresponding question object
        var catAndAnswer = []; 
        catAndAnswer.push (questionsObjects[j].category.toLowerCase()); 
        catAndAnswer.push (questionsObjects[j].rightAnswer.toLowerCase()); 

        columnsQuestionsDictionary[i] = catAndAnswer; 
      } else {
        SpreadsheetApp.getActive().getSheetByName("log").appendRow(["no", columnHeader, question]); 
      }
    }
  }

代码运行良好,我唯一的问题是复杂性,它非常高。在某些情况下,此方法需要将近 6 分钟才能执行(对于这种情况,我有大约 40 列和 7 个问题对象)!为了解耦嵌套循环,我想到将问题值(问题对象数组中的所有对象)连接成 1 个单个字符串,在其中我在每个问题之前加上它在对象数组中的索引。

例如:

  var str = ""; 

  for (j=0; j<questionsObjects.length; j++){

    str = str + j + questionsObjects[j].question.toString.toLowerCase();

  }

然后,我可以通过列标题进行另一个单独的循环,将每个标题提取到一个字符串中,然后使用正则表达式exec 方法来匹配长问题字符串(str)中的那个标题,如果找到它我会得到它在 str 中的索引,然后从中减去 1 以知道它在 objects 数组中的索引。然而,事实证明,匹配正则表达式的复杂度是 O(N),其中 N 是我们搜索的字符串的长度(本例中为 str),假设这将在 columns 循环内,我看到我们仍然会得到很高的复杂度,可以达到 O(N^2)。

如何优化这些嵌套循环,使代码以最有效的方式运行?

【问题讨论】:

  • isStringSimilar 在做什么?` 为什么不将columnHeader 移到外循环?
  • @NinaScholz isStringSimilar 比较两个字符串,它最多允许 3 个字符不匹配。因此,如果两个字符串之间的长度差大于 3,则直接返回 false。如果两个字符串都不为空且不为空,则使用 1 个循环遍历两个字符串并比较相同索引处的字符,如果发现超过 3 个不匹配,则返回 false,否则返回 true。使用的循环迭代 N 次,其中 N 是较长字符串的长度(所以 O(N))。至于移动 columnHeader,我认为这不会降低复杂性,是吗?谢谢
  • 在这种情况下,您需要将每个标题与每个问题进行比较...移动 columnHeader 会有所帮助,因为它只转换一次,而不是列的 n 次。
  • @NinaScholz 我明白了,你是对的,这会有所帮助。我会试试看。谢谢!
  • 感谢@NinaScholz,您的提示非常有用!我发布了一个答案与大家分享结果。

标签: javascript arrays big-o nested-loops


【解决方案1】:

好的,所以我在 cmets 中使用了 Nina Schholz 建议的方式,并将 columnHeader = columnHeaders[0][i].toString().toLowerCase(); 移动到外部循环而不是内部循环中,因为它只需要在外部循环中一。

运行代码所需的时间从 ~295 秒减少到 ~208 秒,这很好。

我还尝试切换循环顺序,将外部循环设为内部循环,将内部循环设为外部循环,并相应地更新 i 和 j 的用法。我这样做是因为总是建议使用较少迭代的外部循环和具有更多迭代的内部循环(根据此resource),在我的情况下,迭代问题对象数组的循环总是期望有数字迭代次数

这是因为如果我们要计算 2 个嵌套循环的复杂度,它将是 (ixj) + i,其中 i 和 j 分别表示外循环和内循环的迭代次数。切换循环顺序不会影响乘法部分(ixj),但会影响加法部分。因此,外部迭代次数总是比内部迭代次数少。

执行此操作后,运行的最后时间变为约 202 秒。

当然,既然现在循环切换了,我将这一行移到了内循环:columnHeader = columnHeaders[0][i].toString().toLowerCase();,但同时我将这个question = questionsObjects[j].question.toString().toLowerCase();移到了外循环下面,因为它只需要在那里。

【讨论】:

    猜你喜欢
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-01
    • 2011-06-15
    • 1970-01-01
    相关资源
    最近更新 更多